2023-05-05 09:11:16 +00:00
|
|
|
/**
|
|
|
|
* Copyright 2023 Google Inc. All rights reserved.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import expect from 'expect';
|
|
|
|
import {TimeoutError} from 'puppeteer-core';
|
|
|
|
import {LocatorEmittedEvents} from 'puppeteer-core/internal/api/Locator.js';
|
|
|
|
import sinon from 'sinon';
|
|
|
|
|
|
|
|
import {
|
|
|
|
getTestState,
|
|
|
|
setupTestBrowserHooks,
|
|
|
|
setupTestPageAndContextHooks,
|
|
|
|
} from './mocha-utils.js';
|
|
|
|
|
|
|
|
describe('Locator', function () {
|
|
|
|
setupTestBrowserHooks();
|
|
|
|
setupTestPageAndContextHooks();
|
|
|
|
|
2023-06-02 17:46:10 +00:00
|
|
|
it('should work with a frame', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
let willClick = false;
|
|
|
|
await page
|
|
|
|
.mainFrame()
|
|
|
|
.locator('button')
|
|
|
|
.on(LocatorEmittedEvents.Action, () => {
|
|
|
|
willClick = true;
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
const button = await page.$('button');
|
|
|
|
const text = await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
});
|
|
|
|
expect(text).toBe('clicked');
|
|
|
|
expect(willClick).toBe(true);
|
|
|
|
});
|
|
|
|
|
2023-05-30 13:20:00 +00:00
|
|
|
it('should work without preconditions', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
let willClick = false;
|
|
|
|
await page
|
|
|
|
.locator('button')
|
|
|
|
.setEnsureElementIsInTheViewport(false)
|
|
|
|
.setTimeout(0)
|
|
|
|
.setVisibility(null)
|
|
|
|
.setWaitForEnabled(false)
|
|
|
|
.setWaitForStableBoundingBox(false)
|
|
|
|
.on(LocatorEmittedEvents.Action, () => {
|
|
|
|
willClick = true;
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
const button = await page.$('button');
|
|
|
|
const text = await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
});
|
|
|
|
expect(text).toBe('clicked');
|
|
|
|
expect(willClick).toBe(true);
|
|
|
|
});
|
|
|
|
|
2023-05-05 09:11:16 +00:00
|
|
|
describe('Locator.click', function () {
|
|
|
|
it('should work', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
let willClick = false;
|
|
|
|
await page
|
|
|
|
.locator('button')
|
|
|
|
.on(LocatorEmittedEvents.Action, () => {
|
|
|
|
willClick = true;
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
const button = await page.$('button');
|
|
|
|
const text = await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
});
|
|
|
|
expect(text).toBe('clicked');
|
|
|
|
expect(willClick).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for multiple selectors', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
let clicked = false;
|
|
|
|
await page
|
|
|
|
.locator('::-p-text(test), ::-p-xpath(/button)')
|
|
|
|
.on(LocatorEmittedEvents.Action, () => {
|
|
|
|
clicked = true;
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
const button = await page.$('button');
|
|
|
|
const text = await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
});
|
|
|
|
expect(text).toBe('clicked');
|
|
|
|
expect(clicked).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work if the element is out of viewport', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button style="margin-top: 600px;" onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
await page.locator('button').click();
|
|
|
|
const button = await page.$('button');
|
|
|
|
const text = await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
});
|
|
|
|
expect(text).toBe('clicked');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work if the element becomes visible later', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button style="display: none;" onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
const button = await page.$('button');
|
|
|
|
const result = page.locator('button').click();
|
|
|
|
expect(
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
})
|
|
|
|
).toBe('test');
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
el.style.display = 'block';
|
|
|
|
});
|
|
|
|
await result;
|
|
|
|
expect(
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
})
|
|
|
|
).toBe('clicked');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work if the element becomes enabled later', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button disabled onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
const button = await page.$('button');
|
|
|
|
const result = page.locator('button').click();
|
|
|
|
expect(
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
})
|
|
|
|
).toBe('test');
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
el.disabled = false;
|
|
|
|
});
|
|
|
|
await result;
|
|
|
|
expect(
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
})
|
|
|
|
).toBe('clicked');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work if multiple conditions are satisfied later', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button style="margin-top: 600px;" style="display: none;" disabled onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
const button = await page.$('button');
|
|
|
|
const result = page.locator('button').click();
|
|
|
|
expect(
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
})
|
|
|
|
).toBe('test');
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
el.disabled = false;
|
|
|
|
el.style.display = 'block';
|
|
|
|
});
|
|
|
|
await result;
|
|
|
|
expect(
|
|
|
|
await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
})
|
|
|
|
).toBe('clicked');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should time out', async () => {
|
|
|
|
const clock = sinon.useFakeTimers();
|
|
|
|
try {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
page.setDefaultTimeout(5000);
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button style="display: none;" onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
const result = page.locator('button').click();
|
|
|
|
clock.tick(5100);
|
|
|
|
await expect(result).rejects.toEqual(
|
|
|
|
new TimeoutError('waitForFunction timed out. The timeout is 5000ms.')
|
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
clock.restore();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should retry clicks on errors', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
const clock = sinon.useFakeTimers();
|
|
|
|
try {
|
|
|
|
page.setDefaultTimeout(5000);
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button style="display: none;" onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
const result = page.locator('button').click();
|
|
|
|
clock.tick(5100);
|
|
|
|
await expect(result).rejects.toEqual(
|
|
|
|
new TimeoutError('waitForFunction timed out. The timeout is 5000ms.')
|
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
clock.restore();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it('can be aborted', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
const clock = sinon.useFakeTimers();
|
|
|
|
try {
|
|
|
|
page.setDefaultTimeout(5000);
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button style="display: none;" onclick="this.innerText = 'clicked';">test</button>
|
|
|
|
`);
|
|
|
|
const abortController = new AbortController();
|
|
|
|
const result = page.locator('button').click({
|
|
|
|
signal: abortController.signal,
|
|
|
|
});
|
|
|
|
clock.tick(2000);
|
|
|
|
abortController.abort();
|
|
|
|
await expect(result).rejects.toThrow(/aborted/);
|
|
|
|
} finally {
|
|
|
|
clock.restore();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2023-05-22 09:57:51 +00:00
|
|
|
|
|
|
|
describe('Locator.hover', function () {
|
|
|
|
it('should work', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<button onmouseenter="this.innerText = 'hovered';">test</button>
|
|
|
|
`);
|
|
|
|
let hovered = false;
|
|
|
|
await page
|
|
|
|
.locator('button')
|
|
|
|
.on(LocatorEmittedEvents.Action, () => {
|
|
|
|
hovered = true;
|
|
|
|
})
|
|
|
|
.hover();
|
|
|
|
const button = await page.$('button');
|
|
|
|
const text = await button?.evaluate(el => {
|
|
|
|
return el.innerText;
|
|
|
|
});
|
|
|
|
expect(text).toBe('hovered');
|
|
|
|
expect(hovered).toBe(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('Locator.scroll', function () {
|
|
|
|
it('should work', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setViewport({width: 500, height: 500});
|
|
|
|
await page.setContent(`
|
|
|
|
<div style="height: 500px; width: 500px; overflow: scroll;">
|
|
|
|
<div style="height: 1000px; width: 1000px;">test</div>
|
|
|
|
</div>
|
|
|
|
`);
|
|
|
|
let scrolled = false;
|
|
|
|
await page
|
|
|
|
.locator('div')
|
|
|
|
.on(LocatorEmittedEvents.Action, () => {
|
|
|
|
scrolled = true;
|
|
|
|
})
|
|
|
|
.scroll({
|
|
|
|
scrollTop: 500,
|
|
|
|
scrollLeft: 500,
|
|
|
|
});
|
|
|
|
const scrollable = await page.$('div');
|
|
|
|
const scroll = await scrollable?.evaluate(el => {
|
|
|
|
return el.scrollTop + ' ' + el.scrollLeft;
|
|
|
|
});
|
|
|
|
expect(scroll).toBe('500 500');
|
|
|
|
expect(scrolled).toBe(true);
|
|
|
|
});
|
|
|
|
});
|
2023-05-23 11:55:12 +00:00
|
|
|
|
|
|
|
describe('Locator.change', function () {
|
|
|
|
it('should work for selects', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setContent(`
|
|
|
|
<select>
|
|
|
|
<option value="value1">Option 1</option>
|
|
|
|
<option value="value2">Option 2</option>
|
|
|
|
<select>
|
|
|
|
`);
|
|
|
|
let filled = false;
|
|
|
|
await page
|
|
|
|
.locator('select')
|
|
|
|
.on(LocatorEmittedEvents.Action, () => {
|
|
|
|
filled = true;
|
|
|
|
})
|
|
|
|
.fill('value2');
|
|
|
|
expect(
|
|
|
|
await page.evaluate(() => {
|
|
|
|
return document.querySelector('select')?.value === 'value2';
|
|
|
|
})
|
|
|
|
).toBe(true);
|
|
|
|
expect(filled).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for inputs', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
await page.setContent(`
|
|
|
|
<input>
|
|
|
|
`);
|
|
|
|
await page.locator('input').fill('test');
|
|
|
|
expect(
|
|
|
|
await page.evaluate(() => {
|
|
|
|
return document.querySelector('input')?.value === 'test';
|
|
|
|
})
|
|
|
|
).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work if the input becomes enabled later', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
|
|
|
|
await page.setContent(`
|
|
|
|
<input disabled>
|
|
|
|
`);
|
|
|
|
const input = await page.$('input');
|
|
|
|
const result = page.locator('input').fill('test');
|
|
|
|
expect(
|
|
|
|
await input?.evaluate(el => {
|
|
|
|
return el.value;
|
|
|
|
})
|
|
|
|
).toBe('');
|
|
|
|
await input?.evaluate(el => {
|
|
|
|
el.disabled = false;
|
|
|
|
});
|
|
|
|
await result;
|
|
|
|
expect(
|
|
|
|
await input?.evaluate(el => {
|
|
|
|
return el.value;
|
|
|
|
})
|
|
|
|
).toBe('test');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for contenteditable', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
await page.setContent(`
|
|
|
|
<div contenteditable="true">
|
|
|
|
`);
|
|
|
|
await page.locator('div').fill('test');
|
|
|
|
expect(
|
|
|
|
await page.evaluate(() => {
|
|
|
|
return document.querySelector('div')?.innerText === 'test';
|
|
|
|
})
|
|
|
|
).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for pre-filled inputs', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
await page.setContent(`
|
|
|
|
<input value="te">
|
|
|
|
`);
|
|
|
|
await page.locator('input').fill('test');
|
|
|
|
expect(
|
|
|
|
await page.evaluate(() => {
|
|
|
|
return document.querySelector('input')?.value === 'test';
|
|
|
|
})
|
|
|
|
).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should override pre-filled inputs', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
await page.setContent(`
|
|
|
|
<input value="wrong prefix">
|
|
|
|
`);
|
|
|
|
await page.locator('input').fill('test');
|
|
|
|
expect(
|
|
|
|
await page.evaluate(() => {
|
|
|
|
return document.querySelector('input')?.value === 'test';
|
|
|
|
})
|
|
|
|
).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for non-text inputs', async () => {
|
|
|
|
const {page} = getTestState();
|
|
|
|
await page.setContent(`
|
|
|
|
<input type="color">
|
|
|
|
`);
|
|
|
|
await page.locator('input').fill('#333333');
|
|
|
|
expect(
|
|
|
|
await page.evaluate(() => {
|
|
|
|
return document.querySelector('input')?.value === '#333333';
|
|
|
|
})
|
|
|
|
).toBe(true);
|
|
|
|
});
|
|
|
|
});
|
2023-05-05 09:11:16 +00:00
|
|
|
});
|