/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const path = require('path');
const zsSelectorEngineSource = require('../lib/generated/zsSelectorEngineSource');
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
describe('Page.$eval', function() {
it('should work with css selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('css=section', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should work with id selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('id=testAttribute', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should work with data-test selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('data-test=foo', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should work with data-testid selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('data-testid=foo', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should work with data-test-id selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('data-test-id=foo', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should work with text selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('text="43543"', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should work with xpath selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('xpath=/html/body/section', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should work with text selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('text=43543', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should auto-detect css selector', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('section', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should auto-detect css selector with attributes', async({page, server}) => {
await page.setContent('43543');
const idAttribute = await page.$eval('section[id="testAttribute"]', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should auto-detect nested selectors', async({page, server}) => {
await page.setContent('
`);
expect(await playwright.selectors._createSelector('z', await page.$('span'))).toBe('"yo"');
// "display: none" makes all children text invisible - fallback to tag name.
await page.setContent(`
`);
expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('" \\"yo "');
});
it('should be case sensitive if quotes are specified', async({page}) => {
await page.setContent(`
yo
ya
\nye
`);
expect(await page.$eval(`text=yA`, e => e.outerHTML)).toBe('
ya
');
expect(await page.$(`text="yA"`)).toBe(null);
});
it('should search for a substring without quotes', async({page}) => {
await page.setContent(`
textwithsubstring
`);
expect(await page.$eval(`text=with`, e => e.outerHTML)).toBe('
textwithsubstring
');
expect(await page.$(`text="with"`)).toBe(null);
});
it('should match input[type=button|submit]', async({page}) => {
await page.setContent(``);
expect(await page.$eval(`text=hello`, e => e.outerHTML)).toBe('');
expect(await page.$eval(`text=world`, e => e.outerHTML)).toBe('');
});
it('should work for open shadow roots', async({page, server}) => {
await page.goto(server.PREFIX + '/deep-shadow.html');
expect(await page.$eval(`text=root1`, e => e.textContent)).toBe('Hello from root1');
expect(await page.$eval(`text=root2`, e => e.textContent)).toBe('Hello from root2');
expect(await page.$eval(`text=root3`, e => e.textContent)).toBe('Hello from root3');
expect(await page.$(`text:light=root1`)).toBe(null);
expect(await page.$(`text:light=root2`)).toBe(null);
expect(await page.$(`text:light=root3`)).toBe(null);
});
});
describe('css selector', () => {
it('should work for open shadow roots', async({page, server}) => {
await page.goto(server.PREFIX + '/deep-shadow.html');
expect(await page.$eval(`css=span`, e => e.textContent)).toBe('Hello from root1');
expect(await page.$eval(`css=[attr="value\\ space"]`, e => e.textContent)).toBe('Hello from root3 #2');
expect(await page.$eval(`css=[attr='value\\ \\space']`, e => e.textContent)).toBe('Hello from root3 #2');
expect(await page.$eval(`css=div div span`, e => e.textContent)).toBe('Hello from root2');
expect(await page.$eval(`css=div span + span`, e => e.textContent)).toBe('Hello from root3 #2');
expect(await page.$eval(`css=span + [attr*="value"]`, e => e.textContent)).toBe('Hello from root3 #2');
expect(await page.$eval(`css=[data-testid="foo"] + [attr*="value"]`, e => e.textContent)).toBe('Hello from root3 #2');
expect(await page.$eval(`css=#target`, e => e.textContent)).toBe('Hello from root2');
expect(await page.$eval(`css=div #target`, e => e.textContent)).toBe('Hello from root2');
expect(await page.$eval(`css=div div #target`, e => e.textContent)).toBe('Hello from root2');
expect(await page.$(`css=div div div #target`)).toBe(null);
expect(await page.$eval(`css=section > div div span`, e => e.textContent)).toBe('Hello from root2');
expect(await page.$eval(`css=section > div div span:nth-child(2)`, e => e.textContent)).toBe('Hello from root3 #2');
expect(await page.$(`css=section div div div div`)).toBe(null);
const root2 = await page.$(`css=div div`);
expect(await root2.$eval(`css=#target`, e => e.textContent)).toBe('Hello from root2');
expect(await root2.$(`css:light=#target`)).toBe(null);
const root2Shadow = await root2.evaluateHandle(r => r.shadowRoot);
expect(await root2Shadow.$eval(`css:light=#target`, e => e.textContent)).toBe('Hello from root2');
const root3 = (await page.$$(`css=div div`))[1];
expect(await root3.$eval(`text=root3`, e => e.textContent)).toBe('Hello from root3');
expect(await root3.$eval(`css=[attr*="value"]`, e => e.textContent)).toBe('Hello from root3 #2');
expect(await root3.$(`css:light=[attr*="value"]`)).toBe(null);
});
});
describe('attribute selector', () => {
it('should work for open shadow roots', async({page, server}) => {
await page.goto(server.PREFIX + '/deep-shadow.html');
expect(await page.$eval(`id=target`, e => e.textContent)).toBe('Hello from root2');
expect(await page.$eval(`data-testid=foo`, e => e.textContent)).toBe('Hello from root1');
expect(await page.$$eval(`data-testid=foo`, els => els.length)).toBe(3);
expect(await page.$(`id:light=target`)).toBe(null);
expect(await page.$(`data-testid:light=foo`)).toBe(null);
expect(await page.$$(`data-testid:light=foo`)).toEqual([]);
});
});
describe('selectors.register', () => {
it('should work', async ({page}) => {
const createTagSelector = () => ({
create(root, target) {
return target.nodeName;
},
query(root, selector) {
return root.querySelector(selector);
},
queryAll(root, selector) {
return Array.from(root.querySelectorAll(selector));
}
});
await playwright.selectors.register('tag', `(${createTagSelector.toString()})()`);
await page.setContent('
');
expect(await playwright.selectors._createSelector('tag', await page.$('div'))).toBe('DIV');
expect(await page.$eval('tag=DIV', e => e.nodeName)).toBe('DIV');
expect(await page.$eval('tag=SPAN', e => e.nodeName)).toBe('SPAN');
expect(await page.$$eval('tag=DIV', es => es.length)).toBe(2);
});
it('should work with path', async ({page}) => {
await playwright.selectors.register('foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') });
await page.setContent('');
expect(await page.$eval('foo=whatever', e => e.nodeName)).toBe('SECTION');
});
it('should work in main and isolated world', async ({page}) => {
const createDummySelector = () => ({
create(root, target) { },
query(root, selector) {
return window.__answer;
},
queryAll(root, selector) {
return [document.body, document.documentElement, window.__answer];
}
});
await playwright.selectors.register('main', createDummySelector);
await playwright.selectors.register('isolated', createDummySelector, { contentScript: true });
await page.setContent('
');
await page.evaluate(() => window.__answer = document.querySelector('span'));
// Works in main if asked.
expect(await page.$eval('main=ignored', e => e.nodeName)).toBe('SPAN');
expect(await page.$eval('css=div >> main=ignored', e => e.nodeName)).toBe('SPAN');
expect(await page.$$eval('main=ignored', es => window.__answer !== undefined)).toBe(true);
expect(await page.$$eval('main=ignored', es => es.filter(e => e).length)).toBe(3);
// Works in isolated by default.
expect(await page.$('isolated=ignored')).toBe(null);
expect(await page.$('css=div >> isolated=ignored')).toBe(null);
// $$eval always works in main, to avoid adopting nodes one by one.
expect(await page.$$eval('isolated=ignored', es => window.__answer !== undefined)).toBe(true);
expect(await page.$$eval('isolated=ignored', es => es.filter(e => e).length)).toBe(3);
// At least one engine in main forces all to be in main.
expect(await page.$eval('main=ignored >> isolated=ignored', e => e.nodeName)).toBe('SPAN');
expect(await page.$eval('isolated=ignored >> main=ignored', e => e.nodeName)).toBe('SPAN');
// Can be chained to css.
expect(await page.$eval('main=ignored >> css=section', e => e.nodeName)).toBe('SECTION');
});
it('should update', async ({page}) => {
await page.setContent('
');
expect(await page.$eval('div', e => e.nodeName)).toBe('DIV');
let error = await page.$('dummy=ignored').catch(e => e);
expect(error.message).toBe('Unknown engine "dummy" while parsing selector dummy=ignored');
const createDummySelector = () => ({
create(root, target) {
return target.nodeName;
},
query(root, selector) {
return root.querySelector('dummy');
},
queryAll(root, selector) {
return Array.from(root.querySelectorAll('dummy'));
}
});
error = await playwright.selectors.register('$', createDummySelector).catch(e => e);
expect(error.message).toBe('Selector engine name may only contain [a-zA-Z0-9_] characters');
await playwright.selectors.register('dummy', createDummySelector);
expect(await page.$eval('dummy=ignored', e => e.id)).toBe('d1');
expect(await page.$eval('css=span >> dummy=ignored', e => e.id)).toBe('d2');
error = await playwright.selectors.register('dummy', createDummySelector).catch(e => e);
expect(error.message).toBe('"dummy" selector engine has been already registered');
error = await playwright.selectors.register('css', createDummySelector).catch(e => e);
expect(error.message).toBe('"css" is a predefined selector engine');
});
});