How to get the current context in Playwright? - playwright

I have a need to open a URL in a new tab. How do I get the current context of Playwright in TypeScript? Here is what I have for C#:
public async Task<int> OpenUrlInNewTab(string url)
{
var newPage = await _context.RunAndWaitForPageAsync(async () =>
{
await ExecuteJavaScript("url => window.open(url);", new[] { url });
});
await newPage.WaitForLoadStateAsync();
return _context.Pages.Count - 1;
}
public async Task<object> ExecuteJavaScript(string javaScript, object? arg = null)
{
return await _page.EvaluateAsync<object>(javaScript, arg);
}

To get the context of a new tab that has opened from your application in Playwright, you need to use browserContext.on('page')
Official docs:
https://playwright.dev/docs/api/class-browsercontext#browser-context-event-page
Here is an example were clicking a button "Ok" will open a new tab and we want to continue the test in the new tab:
/* When "Ok" is clicked the test waits for a new page event and assigns to new page object to a variable called newPage
After this point we want the test to continue in the new tab,
so we'll have to use the newly defined newPage variable when working on that tab
*/
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.locator("span >> text=Ok").click()
])
await newPage.waitForLoadState();
console.log("New page has opened and its url is: " + newPage.url());
// We are working on a new tab now thus calling newPage instead of page
await newPage.locator("#description").type("We got this!");
Notice how we access the initial page with "page.locator" and the new page with "newPage.locator" once we have defined it.

Related

How to navigate to a different content page after a function is called

I have an app that has 2 tabs in Xamarin forms (iOS side). I'd like to know how to make my app navigate to a different content page after a function is called. Let me show you what I mean in code:
these are my two functions in my content page:
protected override async void OnAppearing()
{
base.OnAppearing();
TakePhotoButton_Clicked();
}
async void TakePhotoButton_Clicked()
{
if (App.pictureTaken) return;
App.pictureTaken = true;
//Allows users to take pictures in the app
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
DisplayAlert("No Camera", "Camera is not available.", "OK");
return;
}
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
//Sets the properties of the photo file
SaveToAlbum = true,
PhotoSize = PhotoSize.MaxWidthHeight,
DefaultCamera = CameraDevice.Rear
});
if (file == null)
return;
}
After calling the TakePhotoButton_Clicked() I'd like to force my app to navigate to my other content page.
in pseudocode it would look like:
NavigateTo(MyOtherContentPage);
But im not sure how that would work or if something like that exists. Any suggestions?
Welcome to SO!
If Root page of MainPage is NavigationPage , such as: MainPage = new NavigationPage (new Page1Xaml ()); , you can use Navigation.PushAsync to navigate to another page as follow :
async void OnNextPageButtonClicked (object sender, EventArgs e)
{
await Navigation.PushAsync (new Page2Xaml ());
}
Else if Root page is a normal Page, you can use model naivgation method to navigate to another page as follow:
async void OnItemSelected (object sender, SelectedItemChangedEventArgs e)
{
...
await Navigation.PushModalAsync (detailPage);
}
}
More info can refer to Performing Navigation and Pushing Pages to the Modal Stack.

Why omitting the await keyword results in different behavior?

I am testing some middleware code. When the function middleware.call is provoked I get different results depending on whether I use the keyword await when calling it.
This is testing code:
final actionLog = <dynamic>[];
final Function(dynamic) next = (dynamic action){
actionLog.add(action);
};
test('Fetching a dictionary entry successfully', () async {
DictionaryEntry dictionaryEntry = new DictionaryEntry(2333, 'after', null, null);
when(
mockApi
.getDictionaryEntry('after', any, any)
).thenAnswer((_) => Future.value(dictionaryEntry));
final action = FetchDictionaryEntryAction('after');
await dictionaryMiddleware.call(mocksStore, action, next);
// length of action log is 2
// when I don't use await before calling dictionaryMiddleware.call
// but its 4 when using await before calling the function.
print("length of action log " + actionLog.length.toString());
print(actionLog);
});
This is the middleware:
#override
Future<void> call(
Store<AppState> store, dynamic action, NextDispatcher next) async {
next(action);
if(action is FetchDictionaryEntryAction){
await _fetchDictionaryEntry(action.speechText,
action.fromLanguage, action.toLanguage, next
);
}
}
Future<void> _fetchDictionaryEntry(
String speechText,
String fromLanguage,
String toLanguage,
NextDispatcher next
) async {
if(speechText == null || speechText?.length == 0){
next(ReceivedDictionaryEntryAction(dictionaryEntry: null));
return;
}
next(RequestingDictionaryEntryAction());
try{
final DictionaryEntry dictionaryEntry =
await this.api.getDictionaryEntry(speechText, fromLanguage, toLanguage);
next(ReceivedDictionaryEntryAction(dictionaryEntry: dictionaryEntry));
next(CacheDictionaryEntryAction(dictionaryEntry));
}catch(e){
next(ErrorLoadingDictionaryEntryAction);
}
}
I am wondering why does next() (the dispatcher in the middleware) get called only 2 times when not using await as apposed to getting called 4 times when using await which is the normal behavior.

Dart - is it possible to change a future.forEach. to a map?

So basically i have this piece of working code:
List<User> users = List();
await Future.forEach(querySnapshot.documents, (doc) async {
final snapshot = await doc['user'].get();
users.add(User(id: snapshot["id"], name: snapshot["mail"]));
});
return users;
It's working fine and does exactly what I need but I was wondering if there was a way to somehow change it to a map, such as:
return querySnapshot.documents.map((doc) async {
final snapshot = await doc['user'].get();
User(id: snapshot["id"], name: snapshot["mail"]);
}).toList{growable: true};
The problem when I do that is that it says: a value of type List< Future< Null>> can't be assigned to a variable of type List< User>.
So I was wondering if it was possible or if the only way is with a Future.forEach
To turn a list of Futures into a single Future, use Future.wait from dart:async. (Also don't forget to return the user object).
final List<User> = await Future.wait(querySnapshot.documents.map((doc) async {
final snapshot = await doc['user'].get();
return User(id: snapshot["id"], name: snapshot["mail"]);
}));

javaFX webview window.onload is fired before loadworker succeeds

I use a JavaFX webview in my application. With the following code I set a member after the page has been loaded
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) {
if (newState == Worker.State.SUCCEEDED) {
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("mymember", new JavaScriptBridge(this));
}
}
});
Now in javascript I can invoke mymember.doSomething() e.g. called when I press the button and it's executed successfully, but if I add the following code to the html
<script>
function startup() {
mymember.doSomething();
}
window.onload=startup;
</script>
It's not executed automatically when the page is loaded. It seems like window.onload is executed before the LoadWorker gets notified. So mymember is not set yet. But on the other hand, I cannot set mymember before the html has been loaded, right?
Any idea when I need to set mymember to have it ready when window.onload is executed?
Thanks!
Maybe it's too late for an answer to this problem, but after answering this question, I've been trying to find a reason why executeScript has to be called after the complete load of the webpage.
So I've done this test:
public class EarlyWebEngineTest extends Application {
#Override
public void start(Stage stage) {
final WebView webView = new WebView();
final WebEngine webEngine = webView.getEngine();
// Early call of executeScript to get a JavaScript object, a proxy for the
// Java object to be accessed on the JavaScript environment
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", new JavaApplication());
webEngine.getLoadWorker().stateProperty().addListener((ov,oldState,newState)->{
if(newState==State.SCHEDULED){
System.out.println("state: scheduled");
} else if(newState==State.RUNNING){
System.out.println("state: running");
} else if(newState==State.SUCCEEDED){
System.out.println("state: succeeded");
}
});
Button button=new Button("Load Content");
button.setOnAction(e->webEngine.loadContent("<html>"
+ " <script>function initialize() {"
+ " var nameVar = \"This is a JS var\"; "
+ " app.callJavascript(nameVar);"
+ "} </script>"
+ " <body onLoad=\"initialize()\">Hi, this is a test!</body>"
+ "</html>"));
VBox vbox = new VBox(10,button,webView);
Scene scene = new Scene(vbox,400,300);
stage.setScene(scene);
stage.show();
}
public class JavaApplication {
public void callJavascript(String msg){
System.out.println("JS>> "+msg);
}
}
public static void main(String[] args) {
launch(args);
}
}
The content is not loaded until the button is clicked, but we've already created the JavaScript object on the browser.
Before clicking the button, there's nothing on the output console. But if we click the button... this is the output:
state: scheduled
state: running
JS>> This is a JS var
state: succeeded
As we can see, the Java object is effectively passed to the script before the latter is executed, and app.callJavascript is successfully called while the content is being loaded.
Note that for the common purpose of accessing the loaded DOM, the usual approach of calling executeScript after State.SUCCEEDED is still the recommended way.
Woks for all (including subsequent) pages:
Where "java" is set in JAVA code:
window.setMember("java", new JavaApplication());
HTML (subsequent) page, keep waiting for 100ms if var "java" is not set (externally by JAVA):
<script>
function init(){
if (typeof java !== 'undefined') {
java.doSomething();
}else{
setTimeout(function(){ init() }, 100 );
}
}
$(document).ready(function(){
init();
});
</script>
Yes, loadworker always execute after window.onload or document.onload.
The workaround you can try, you can create new listener in javascript, for example so-called:
document.addEventListener("deviceready", function(){
MyJavaBridge.executeJavaMethod();
});
And then in your loadworker, you can do this:
webview.getEngine().getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
JSObject window = (JSObject) webview.getEngine().executeScript("window");
System.out.println("window : " + window);
window.setMember("MyJavaBridge", javaBridge);
webview.getEngine().executeScript("const event = new Event('deviceready');document.dispatchEvent(event);");
}
});
As you can see, you execute this webview.getEngine().executeScript("const event = new Event('deviceready');document.dispatchEvent(event);"); after setMember, so instead of initialise your work in window.onload, you can do it in your custom event listener deviceready, so you can have better control on the sequence of page load and java side loadworker.
This is exactly how cordova doing, this idea is coming from it.
JQuery document.ready vs Phonegap deviceready

Distinguish between onClick and onDoubleClick on same element to perform different actions in Dart

I want to do the ill-advised and place both an onClick and onDoubleClick on the same element with each type of event resulting in a different action. Specifically on an image, click to advance to the next image, double-click to toggle fullscreen.
Naturally I get two clicks followed by a double-click (though I understand that some browsers only fire one click before the double-click).
I had thought to make it easy on myself and place each event into a buffer (List) - or rather to add the event.type string to a list, then, after a suitable elapse of time, say 250 or 300 milliseconds examine the last item in the buffer and if doubleclick then go fullscreen else advance the length of the list.
I have found that the list only ever has one item, and I have not worked out how to get the timer to work..
Amongst my attempts was this one:
void catchClickEvents(Event e) {
var eventTypes = new List<String>();
eventTypes.add(e.type);
Duration duration = const Duration(milliseconds: 300);
var timeout = new Timer(duration, () => processEvents(eventTypes));
}
void processEvents(List eTypes) {
// just to see what is going on...
print(eTypes);
}
this results in this output printed to the console:
[click]
[click]
[dblclick]
rather than
[click, click, dblclick]
If I slow it down there is a clear delay before those three event types are printed together
So...
The bigger question is
'What is the darty way to distiguish between single and double-click and perform a different action for each?'
The other questions are:
How do I fill a buffer with successive events (and later clear it down) - or even how do I use Dart's Stream of events as a buffer...
What is the real way timeout before examining the contents of the buffer?
(and I guess the final question is 'should I abandon the effort and settle for a conventional set of buttons with glyph-icons?'!)
I'm not sure if IE still has the event sequence explained here (no 2nd click event)
https://stackoverflow.com/a/5511527/217408
If yes you can use a slightly deviated variant of Roberts solution:
library app_element;
import 'dart:html' as dom;
import 'dart:async' as async;
Duration dblClickDelay = new Duration(milliseconds: 500);
async.Timer clickTimer;
void clickHandler(dom.MouseEvent e, [bool forReal = false]) {
if(clickTimer == null) {
clickTimer = new async.Timer(dblClickDelay, () {
clickHandler(e, true);
clickTimer = null;
});
} else if(forReal){
print('click');
}
}
void dblClickHandler(dom.MouseEvent e) {
if(clickTimer != null) {
clickTimer.cancel();
clickTimer = null;
}
print('doubleClick');
}
void main() {
dom.querySelector('button')
..onClick.listen(clickHandler)
..onDoubleClick.listen(dblClickHandler);
}
or just use Roberts solution with the mouseUp event instead of the click event.
The problem is that your variable is not global.
var eventTypes = new List<String>();
void catchClickEvents(Event e) {
eventTypes.add(e.type);
Duration duration = const Duration(milliseconds: 300);
var timeout = new Timer(duration, () => processEvents(eventTypes));
}
void processEvents(List eTypes) {
print(eTypes);
}
There also is e.detail that should return the number of the click. You can use that, if you don't need the Internet Explorer. The problem with your list is that it grows and never gets cleared.
Let's take into account what we know: You get click events and at somepoint you have doubleclicks.
You could use a click counter here. (Or use e.detail) to skip the second click event. So you only have click and dblclick.
Now when you get a click event, you create a new timer like you did before and run the click action. If you get the dblclick event you simply run you action. This could like this:
DivElement div = querySelector('#div');
Timer timeout = null;
div.onClick.listen((MouseEvent e) {
if(e.detail >= 2) {
e.preventDefault();
} else {
if(timeout != null) {
timeout.cancel();
}
timeout = new Timer(new Duration(milliseconds: 150), () => print('click'));
}
});
div.onDoubleClick.listen((MouseEvent e) {
if(timeout != null) {
timeout.cancel();
timeout = null;
}
print('dblclick');
});
This is the example code that works for me. If you can't rely on e.detail just us a counter and reset it after some ms after a click event.
I hope this helps you.
Regards, Robert
Your page should react on user inputs as fast as possible. If you wait to confirm double click - your onClick will become much less responsive. You can hide the problem by changing visual state of the element(for example, playing animation) after first click but it can confuse user. And it gets even worse with handheld. Also if element has to react only on onClick event, you can "cheat" and listen to onmousedown instead - it will make your UI much more responsive.
On top of all this, double click, from client to client, may have noticeably different trigger time interval and it isn't intuitive, for user, that you can double click something. You will have to bloat your interface with unnecessary hints.
edit: Added my solution. It should be fairly extensible and future proof.
import 'dart:html';
import 'dart:async';
import 'dart:math';
//enum. Kinda... https://code.google.com/p/dart/issues/detail?id=88
class UIAction {
static const NEXT = const UIAction._(0);
static const FULLSCREEN = const UIAction._(1);
static const ERROR = const UIAction._(2);
final int value;
const UIAction._(this.value);
}
/**
*[UIEventToUIAction] transforms UIEvents into corresponding UIActions.
*/
class UIEventToUIAction implements StreamTransformer<UIEvent, UIAction> {
/**
* I use "guesstimate" value for [doubleClickInterval] but you can calculate
* comfortable value for the user from his/her previous activity.
*/
final Duration doubleClickInterval = const Duration(milliseconds: 400);
final StreamController<UIAction> st = new StreamController();
Stream<UIAction> bind(Stream<UIEvent> originalStream) {
int t1 = 0,
t2 = 0;
bool isOdd = true;
Duration deltaTime;
originalStream.timeout(doubleClickInterval, onTimeout: (_) {
if ((deltaTime != null) && (deltaTime >= doubleClickInterval)) {
st.add(UIAction.NEXT);
}
}).forEach((UIEvent uiEvent) {
isOdd ? t1 = uiEvent.timeStamp : t2 = uiEvent.timeStamp;
deltaTime = new Duration(milliseconds: (t1 - t2).abs());
if (deltaTime < doubleClickInterval) st.add(UIAction.FULLSCREEN);
isOdd = !isOdd;
});
return st.stream;
}
}
void main() {
//Eagerly perform time consuming tasks to decrease the input latency.
Future NextImageLoaded;
Future LargeImageLoaded;
element.onMouseDown.forEach((_) {
NextImageLoaded = asyncActionStub(
"load next image. Returns completed future if already loaded");
LargeImageLoaded = asyncActionStub(
"load large version of active image to show in fullscreen mode."
"Returns completed future if already loaded");
});
Stream<UIEvent> userInputs = element.onClick as Stream<UIEvent>;
userInputs.transform(new UIEventToUIAction()).forEach((action) {
switch (action) {
case UIAction.FULLSCREEN:
LargeImageLoaded.then( (_) =>asyncActionStub("fullscreen mode") )
.then((_) => print("'full screen' finished"));
break;
case UIAction.NEXT:
NextImageLoaded.then( (_) =>asyncActionStub("next image") )
.then((_) => print("'next image' finished"));
break;
default:
asyncActionStub("???");
}
});
}
final DivElement element = querySelector("#element");
final Random rng = new Random();
final Set performed = new Set();
/**
*[asyncActionStub] Pretends to asynchronously do something usefull.
* Also pretends to use cache.
*/
Future asyncActionStub(String msg) {
if (performed.contains(msg)) {
return new Future.delayed(const Duration(milliseconds: 0));
}
print(msg);
return new Future.delayed(
new Duration(milliseconds: rng.nextInt(300)),
() => performed.add(msg));
}

Resources