How to get a collection of elements with playwright? - javascript

How to get all images on the page with playwright?
I'm able to get only one (ElementHandle) with following code, but not a collection.
const { chromium } = require("playwright");
class Parser {
async parse(url) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(url);
await page.waitFor("img");
// TODO: get somehow collection of elements
return await page.$("img");
}
}
module.exports = Parser;
Somewhere in another module far far away:
const Parser = require("./path/to/dir/Parser.js");
const parser = new Parser();
parser
.parse(body.url)
.then(elemHandle => {
// here I get only one ElementHandle object, but suppose to get an array or collection
})
.catch(err => {
throw new Error(err);
});
Node v.12.16.1

I have already found the answer. Need to use page.$$(selector) instead of page.$(selector) to grab like document.querySelectorAll(selector).

As mentioned in the accepted answer, you can use await page.$$(selector).
Here is a link to the page.$$ official documentation
You can also use the following code.
const result = await page.evaluate(selector => document.querySelectorAll(selector) , selector);
Here is a link to the page.evaluate official documentation

for playwright use: await page.$$(selector);

There is another way to work with a list of elements you can read from the documentation. And I like it much more
https://playwright.dev/docs/locators#lists
So you just select using the page.locator and after that, you can interact with each element using for loop or selected the needed element using .nth()

For counting, when trying to avoid the use of await page.$$(selector);, another alternative is to directly use LocatorAssertion:
await expect(locator).toHaveCount(n);
Link to official documentation

Locator seems to be the current way forward. page.$$(img) is discouraged.
Instead use:
const imageList = await page.locator('img);
For count:
const imageList = await page.locator('img');
console.log('images: ', await imageList.count());
More information on https://playwright.dev/docs/locators

Related

Is it possible to create custom commands in Playwright?

I'm looking for a way to write custom commands in Playwright like it's done in Cypress. Playwright Issues has one page related to it but I have never seen any code example.
I'm working on one test case where I'm trying to improve code reusability. Here's the code:
import { test, chromium } from '#playwright/test';
config();
let context;
let page;
test.beforeEach(async () => {
context = await chromium.launchPersistentContext(
'C:\\Users\\User\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default',
{
headless: false,
executablePath: 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
}
);
page = await context.newPage();
await page.goto('http://localhost:3000/');
});
test.afterEach(async () => {
await page.close();
await context.close();
});
test('Sample test', async () => {
await page.click('text=Open popup');
await page.click('_react=Button >> nth=0');
await page.click('text=Close popup');
});
I'm trying to create a function that will call hooks test.beforeEach() and test.afterEach() and the code inside them.
In the Playwright Issue page, it says that I need to move it to a separate Node module and then I would be able to use it but I'm struggling to understand how to do it.
The example you're giving can be solved by implementing a custom fixture.
Fixtures are #playwright/test's solution to customizing/extending the test framework. You can define your own objects (similar to browser, context, page) that you inject into your test, so the test has access to it. But they can also do things before and after each test such as setting up preconditions and tearing them down. You can also override existing fixtures.
For more information including examples have a look here:
https://playwright.dev/docs/test-fixtures

Puppeteer identifier string variable won't parse; Unsure why

I'm trying to have a string be used inside a puppeteer string, it won't work for some reason.
Specifically with this code
await page.waitForSelector('div[class = "sh-dlr__list-result"')
When i try to parse in a variable
let identified1 = 'div[class = "sh-dlr__list-result"'
so making
await page.waitForSelector(identified1)
It won't work. This is really limiting, is there a way around this issue?
This is the expanded code
https://jsfiddle.net/hewlbern/6p7kdozt/10/
Run it in your computer, jsfiddle unsure if I can run it from there.
I believe it is creating a cors error now - very weird! Why would using a variable create a cors error : /
Thanks!
The reason is because you're declaring identified inside the page.evaluate(). So, when you do the following it's already out of scope.
if (currentPage < pagesToScrape) {
console.log(identified1);
await Promise.all([
await page.click(buttonSelector),
await page.waitForSelector(identified),
]);
}
You did log the identified1 but you're using identified for the selector.
You'll have to pass the identifier2 to the pageFunction like so:
let newProducts = await page.evaluate(({ identifier2 }) => {
// ...
},{ identifier2 });
See here some examples:

How to grab 2nd element of querySelectorAll and loop thru its elements using Puppeteer node js

Im trying to scrape this website, their website layout uses the same class name for the information that I need. I tried using document.querySelectorAll() but it returns undefined.
let shoeHtml = await page.evaluate( () => document.querySelectorAll('.form-section-right'))
If i try let shoeHtml = await page.evaluate( () => document.querySelectorAll('.form-section-right')[1].innerHTML) it returns a string and I'm unable to loop thru it and grab the information I need
Heres the html code. https://pastebin.com/dHbbu5EG
The information I want to grab is the a tags. I would also like to click them.
evaluate only returns serializable data and in-page objects are not serializable. You need to either use evaluateHandle or page.$$ which is the equivalent of querySelectorAll in the puppeteer execution context.
let shoeHtml = await page.$$('.form-section-right');
Accessing element's properties in puppeteer context:
const innerHTML = await page.evaluate(el => el.innerHTML, shoeHtml[1]);
or
const innerHTML = await (await shoeHtml[1].getProperty('innerHTML')).jsonValue();

What should I use instead of toPromise() when using await on an Observable?

This page says "toPromise has been deprecated! (RxJS 5.5+)" but I've been using it lately with AngularFire2 (when I only want one result) like this:
const foo = await this.afs.doc(`docPath`).valueChanges().toPromise();
Should I not be doing this? If not, what is the await alternative?
UPDATE:
After the answer below I've changed this:
const foo = await this.afs.doc(`docPath`).valueChanges().toPromise();
...to this:
const foo = await (new Promise(resolve => this.afs.doc(`docPath`).valueChanges().pipe(first()).subscribe(result => resolve(result))));
Could someone please explain to me how this is an improvement?! Seems like a step backward to me.
You just should put after pipe!
.pipe(take(1)).toPromise
Just a few other options for those who want to be crazy:
const foo = await this.afs.doc(`docPath`).valueChanges().pipe(take(1)).toPromise();
or
const foo = (await this.afs.doc('docPath').get().toPromise()).data();
or
const foo = (await this.afs.doc('docPath').get().pipe(take(1)).toPromise()).data();
or
const foo = (await this.afs.doc('docPath').snapshotChanges().pipe(take(1))
.toPromise()).payload.data();
But the shortest is:
const foo = (await this.afs.doc('docPath').ref.get()).data();
And anywhere you can use take(1) you can use first() if you want to emit an error.
For more Firebase promises, see here.
J
firstValueFrom and lastValueFrom is definitly a better alternative for many reasons:
The naming is more readable and self explanatory.
The additional ability to select either first or last value.
The additional ability to declare a default value in case the observable didn't emit any
Reference: https://stackoverflow.com/a/69215890/5191209

TestCafe - How to check if a web element exists or does not exist without failing the test?

I'm trying to write a script that needs to adapt it's workflow behavior depending on whether a particular browser object found by CSS selector exists or does not exist.
I do not want to use a document.getElementByID method as this is not technically a CSS selector, and our entire enterprise is standardized on CSS selector so anything that walks the DOM other then a CSS selector won't make it past our code review process anyway.
var thing = await things.thingSelector(thingName);
if (await t.expect(thing.exists).notOk()) {
await t.click(things.OpenThing(thingName));
} else {
return false;
}
return true;
Where thingSelector is:
const thingSelector = name =>
Selector('p.thing-header-title span')
.withText(name)
.parent('div.thing');
Where OpenThing is:
const OpenThing = name =>
thingSelector(name)
.find('a.thing-footer-item')
.withText('Open');
I need to be able to continue execution if the object is not there and I'm checking that it exists, or if the object is there and I'm checking that it does not exist, and also the cases where the object is not there and it does not exist and the object is not there and I'm checking that it does not exist.
In all cases I still need to proceed with the workflow.
I've tried both sides of the logic coin:
if (!await t.expect(thing.exists).ok())
And
if (await t.expect(thing.exists).notOk())
If one of the above doesn't fail in one scenario it will fail in the other, and the other one will fail in the scenario that the first one didn't fail. I need something that will give me the logic, but not ever fail the script execution and still allow me to return either True or False depending on if the object is present or not present.
Thank you in advance for helping me to solve this problem, and also to learn and grow in my Javascript skills!
You can check the async exists property in the if condition in the following way:
if(await things.thingSelector(thingName).exists) {
// do something
}
You can use the following assertions to test existence and non-existence elements:
test('Test existence and non-existence elements', async t => {
await t
.expect(Selector('#existing-element').exists)
.expect(Selector('#non-existing-element').exists).notOk()
});
How to check if an element exists:
test('Element with id "element" exists', async t => {
await t.expect(Selector('#element').exists).ok(); });
How to check if an element does NOT exist:
test('Element with id "element" shouldn\'t exist', async t => {
await t.expect(Selector('#element').exists).notOk(); });
Check out the official documentation.
this is working for me, you can give it a try
async veriryCreativeIconButtonNotExists(){
await t.expect(this.exportButton.exists).ok()
await t.expect(this.columnPickerIcon.exists).ok()
await t.expect(this.filterColumnIcon.exists).ok()
if (await t.expect(this.columnPickerIcon.exists).ok()){
await t.expect(this.creavtiveIconButton.exists).ok()
await t.click(this.creavtiveIconButton)
await t.expect(this.creativeImage.exists).ok()
await t.click(this.creativeImage)
} else {
return false;
}
return true;

Categories

Resources