I'm trying to write a simple e2e test.
import {chromium, webkit, firefox} from "playwright";
(async () => {
for (const browserType of [chromium, firefox, webkit]) {
const browser = await browserType.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("https://github.com");
await page.fill('input[aria-label="Search GitHub"]', "Playwright");
await page.press('input[aria-label="Search GitHub"]', "Enter");
await page.click(".repo-list-item:nth-child(1) a");
await page.click('span[data-content="Security"]');
await page.screenshot({path: `screenshots/security-${browserType.name()}.png`, fullPage: false});
await page.close();
await browser.close();
}
})();
This code is working fine with chromium and firefox. However, when I use webkit it breaks on the second screen.
Webkit error logs:
Error.captureStackTrace(stackObject);
^
page.click: Protocol error (Runtime.awaitPromise): The page has been closed.
=========================== logs ===========================
waiting for selector ".repo-list-item:nth-child(1) a"
selector resolved to visible <a class="v-align-middle" href="/microsoft/playwrig…>…</a>
attempting click action
waiting for element to be visible, enabled and not moving
element is moving - waiting...
element is visible, enabled and does not move
scrolling into view if needed
done scrolling
checking that element receives pointer events at (468,222)
<div class="mr-3">↵ Apache-2.0 license↵ </div> from <div>…</div> subtree intercepts pointer events
retrying click action
waiting for element to be visible, enabled and not moving
element is moving - waiting...
element is visible, enabled and does not move
scrolling into view if needed
done scrolling
checking that element receives pointer events at (468,222)
<div class="mr-3">↵ Apache-2.0 license↵ </div> from <div>…</div> subtree intercepts pointer events
retrying click action
I'm on Windows 10.
Related
I'm trying to click a button on a website with puppeteer but it doesn't work for me.
Element-info:
<button aria-label="Alles akzeptieren" role="button" data-testid="uc-accept-all-button" class="sc-gtsrHT gqGzpd">OK</button>
My Code:
async function checkout(){
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto(product_url);
await page.waitFor(3000);
await page.click("Button[class='sc-gtsrHT gqGzpd']", elem => elem.click());
}
Error Message:
Error: No node found for selector: Button[class='sc-gtsrHT gqGzpd']
at Object.assert (C:\Coding\Ticket-Bot\node_modules\puppeteer\lib\cjs\puppeteer\common\assert.js:26:15)
at DOMWorld.click (C:\Coding\Ticket-Bot\node_modules\puppeteer\lib\cjs\puppeteer\common\DOMWorld.js:277:21)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async checkout (C:\Coding\Ticket-Bot\bayern.js:14:5)
Pictures:
https://i.stack.imgur.com/BYOnx.png (Button Info)
https://i.imgur.com/of9Rjgo.png (Button)
what is the correct code so that the button will be clicked?
You get the error due to the element does not exist on the page, it may be caused by the fact that CSS classes are autogenerated as #Sirko suggests.
You can even inspect the element's class name in DevTools if you launch puppeteer in headful mode.
You will need to find those selectors that will remain the same, e.g.:
await page.click('[aria-label="Alles akzeptieren"]');
await page.click('[data-testid="uc-accept-all-button"]');
Note: I am not sure if you need elem => elem.click() in the click options.
I have a website which has some data as HTML (loaded via ajax) and I have to convert that to JSON with custom formatting.
So, for this I choose Puppeteer.
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
const page = await browser.newPage();
This web API that I'm developing will be having concurrent web requests, so I thought browser.close() might affect the other concurrent requests, so I decide to do only page.close.
One problem that I'm facing is when I do puppeteer.launch, for each request it opens two about:blank tab in a new window.
And when browser.newPage() is requested it returns one of the blank tabs and leaves the other one opened.
That leads to multiple window opened with multiple about:blank.
Here I don't know the right way to handle this, I can't close the browser because it will close all the pages which are being used by other requests.
You are seeing an empty (about:blank) tab each time you run this code, because you are doing two things here:
Launching a new browser - which already starts with an open tab
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
Opening a new tab.
const page = await browser.newPage();
If you don't want to have "zombie" blank tabs, then you can just reuse the initial tab like this:
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
const currentPages = await browser.pages(); // list the opened tabs
const [page] = currentPages; // use the first (and only) opened tab.
Note that in this case, since you are just reusing the only one and initial tab, closing it with page.close() will have the same effect as closing the browser with browser.close().
Exploring some Express + Browser concurrency alternatives
Consider that a different solution would take place if you want to reuse the same browser instance for the lifetime of your Express server, ie. serve all requests on the same browser, or if you want to use a new browser instance for each individual request.
1. One browser instance per server
In this case it might make sense, depending on your requirements, in managing one tab per request.
// launch the browser instance, once
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
// handle incoming requests
app.get("/foo", async (req, res) => {
const page = await browser.newPage();
try {
// ... execute some logic on this new page
} catch(error) {
// whoops, logic went wrong, respond with 500 or something
} finally {
// cleanup: close the opened tab, no matter how the logic resulted
await page.close()
}
})
Note that still in this scenario, the browser context would be shared across the pages, for example cookies, local storage, and so on. You have to consider this if you plan to allow concurrent requests that also can have conflicts in reusing the same shared context.
2. One browser instance per request
In this scenario you launch a new browser instance per request, you ensure each request will have a clean context and won't collide with other possible requests.
app.get("/foo", async (req, res) => {
// launch the browser instance, one per request
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
// no need to open a new tab, reuse the first one
const [page] = await browser.pages();
try {
// ... execute some logic on the page
} catch(error) {
// whoops, logic went wrong, respond with 500 or something
} finally {
// cleanup: close the browser
// await page.close() // (not really needed if you will close the entire browser,
// and would have the same effect as browser.close()
// if you haven't opened more tabs)
await browser.close()
}
})
But consider that spining a new browser process up would also be more resource-intensive, and your request would take more time to resolve, compared to reusing an already available browser process.
EDIT: code formatting.
I am trying to click on a button but for some weird reason, I am not able to. Here is the code.
import { ClientFunction } from 'testcafe';
import { Selector } from 'testcafe';
fixture `Fixture`
.page `https://viewshape.com/shapes/12b5ntdyuf7`
test(`test`, async t => {
await t.maximizeWindow();
await t.wait(3000);
const fullScreen = Selector('.sp-controls-btn.js-sp-fullscreen-button').filterVisible();
//await t.debug();
await t
.expect(fullScreen.with({visibilityCheck: true}).exists)
.ok({timeout: 30000})
.hover(fullScreen)
.click(fullScreen);
await t.wait(4000);
});
But if I go through debug mode using .debug() and then use Next-Action option in the debugger, the .click() works.
I am not able to understand what is going on here. Why is it .click() is working in .debug() but not in normal flow.
When the Testcafe click action is executed on a DOM element, it calls the element.click() method. Mentioned 'Failed to execute 'requestFullscreen' on 'Element' error means that click event handler calls the requestFullscreen method, which must be called inside a user interaction only. This is a browser's security restriction and there is no way to overcome it.
I'm running into an issue where calling resume on an AudioContext never resolves when attempting to play audio in Safari. I'm creating an AudioContext on page load, thus it starts in a suspended state.
According to this chromium issue, calling resume will not resolve if blocked by the browser's autoplay policy. I have a click event bound to a <button> that will resume the context if it's suspended. This works in both Firefox and Chrome, and will work in Safari if I change the autoplay settings for the site.
Below is how I would normally resume the context:
await context.resume();
For Safari, I've tried calling resume without waiting for the promise to resolve, and instead register a callback for onstatechange, which is then wrapped in a Promise:
if (window.webkitAudioContext) {
await new Promise((accept, reject) => {
this.context.onstatechange = async () => {
if ((await this.context.state) === "playing") {
accept();
} else {
reject();
}
};
this.context.resume();
});
}
However, nothing has changed: the Promise never resolves, which means that the context state isn't changing.
Am I missing something?
iOS will only allow audioContext to be resumed if it is running within the call-stack of a UI Event Handler. Running it within a Promise moves the call to another call-stack.
Also, audioContext.resume() returns a promise, which must be awaited.
Try this:
onPlayHandler() {
alert("State before: " + this.audioContext.state);
await this.audioContext.resume();
alert("State after: " + this.audioContext.state);
}
I finally managed to get audio playing on Safari.
The "fix" was rather simple: I had to call resume within the event handler that is bound to the element being used to initiate playback. In my scenario, I was using Redux with React, so a dispatch would be made, and resuming the context would happen at a later time within another component. I'm guessing that Safari didn't see this as a direct response to a user interaction event, so it kept the context in a suspended state.
Was wondering if anyone has had a similar issue.
In the app I'm working with, we have a spinner showing downloading content with a stop button in the middle. When the user taps the spinner/stop button, the download is meant to cancel. For reference the spinner/stop button looks like this on iOS:
I'm trying to write an e2e test for this functionality using Detox. It doesn't work using automatic synchronisation as the animation (and the download) keeps the thread running. I've tried using device.disableSynchronization() but I haven't had any success.
Here's my e2e test for reference:
it('should start and then cancel a download from the <My Learning> screen', async() => {
// setup
await device.reloadReactNative()
await expect(element(by.id('feed_screen'))).toBeVisible()
await element(by.id('LearningPlan_Tab')).tap()
await expect(element(by.id('learning-plan-screen'))).toBeVisible()
// Tap the download icon, it will turn into a spinner
await element(by.id('offline_download_c2a')).atIndex(1).tap()
// Alert box appears with Cancel/Download options
await expect(element(by.label('Download')).atIndex(1)).toBeVisible()
await element(by.label('Download')).atIndex(1).tap()
// Ideally this would work, but it doesn't (the above alert box doesn't dismiss properly)
await device.disableSynchronization()
await waitFor(element(by.id('download_spinner_c2a')).atIndex(0)).toBeVisible().withTimeout(5000)
await element(by.id('download_spinner_c2a')).atIndex(0).tap()
await device.enableSynchronization()
await element(by.label('Cancel download')).tap()
await expect(element(by.id('offline_download_c2a')).atIndex(1)).toBeVisible()
})
When this test runs the app still seems to wait for the download to finish. Does anyone know any suggestions on the best way to test this, or if it's even possible?
I've found a way to make it work, though it seems quite flaky:
it('should start and then cancel a download from the <My Learning> screen', async () => {
//...
await device.disableSynchronization()
// This timeout seems to help
await waitFor(element(by.id('download_spinner_c2a')).atIndex(0)).toBeVisible().withTimeout(10000)
await element(by.id('download_spinner_c2a')).atIndex(0).tap()
await device.enableSynchronization()
await element(by.label('Cancel download')).tap()
await expect(element(by.id('offline_download_c2a')).atIndex(1)).toBeVisible()
})