api(waitForSelector): make "state: visible" default, includes rename to state (#2091)

This commit is contained in:
Pavel Feldman 2020-05-04 11:03:44 -07:00 committed by GitHub
parent 1f0217986e
commit bcce48362a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 75 additions and 63 deletions

View file

@ -1811,11 +1811,11 @@ return finalResponse.ok();
#### page.waitForSelector(selector[, options]) #### page.waitForSelector(selector[, options])
- `selector` <[string]> A selector of an element to wait for - `selector` <[string]> A selector of an element to wait for
- `options` <[Object]> - `options` <[Object]>
- `waitFor` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `attached`. - `state` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `visible`.
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector satisfies `waitFor` option. Resolves to `null` if waiting for `hidden` or `detached`. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector satisfies `state` option. Resolves to `null` if waiting for `hidden` or `detached`.
Wait for the `selector` to satisfy `waitFor` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw. Wait for the `selector` to satisfy `state` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw.
Element is considered `visible` when it has non-empty bounding box and no `visibility:hidden`. Note that element without any content or with `display:none` has an empty bounding box and is not considered visible. Element is considired `hidden` when it is not `visible` as defined above. Element is considered `visible` when it has non-empty bounding box and no `visibility:hidden`. Note that element without any content or with `display:none` has an empty bounding box and is not considered visible. Element is considired `hidden` when it is not `visible` as defined above.
@ -2435,11 +2435,11 @@ const [response] = await Promise.all([
#### frame.waitForSelector(selector[, options]) #### frame.waitForSelector(selector[, options])
- `selector` <[string]> A selector of an element to wait for - `selector` <[string]> A selector of an element to wait for
- `options` <[Object]> - `options` <[Object]>
- `waitFor` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `attached`. - `state` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `visible`.
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector satisfies `waitFor` option. Resolves to `null` if waiting for `hidden` or `detached`. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector satisfies `state` option. Resolves to `null` if waiting for `hidden` or `detached`.
Wait for the `selector` to satisfy `waitFor` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw. Wait for the `selector` to satisfy `state` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw.
Element is considered `visible` when it has non-empty bounding box and no `visibility:hidden`. Note that element without any content or with `display:none` has an empty bounding box and is not considered visible. Element is considired `hidden` when it is not `visible` as defined above. Element is considered `visible` when it has non-empty bounding box and no `visibility:hidden`. Note that element without any content or with `display:none` has an empty bounding box and is not considered visible. Element is considired `hidden` when it is not `visible` as defined above.

View file

@ -204,18 +204,18 @@ You can explicitly wait for an element to appear in the DOM or to become visible
```js ```js
// Wait for #search to appear in the DOM. // Wait for #search to appear in the DOM.
await page.waitForSelector('#search', { waitFor: 'attached' }); await page.waitForSelector('#search', { state: 'attached' });
// Wait for #promo to become visible, for example with `visibility:visible`. // Wait for #promo to become visible, for example with `visibility:visible`.
await page.waitForSelector('#promo', { waitFor: 'visible' }); await page.waitForSelector('#promo');
``` ```
... or to become hidden or detached ... or to become hidden or detached
```js ```js
// Wait for #details to become hidden, for example with `display:none`. // Wait for #details to become hidden, for example with `display:none`.
await page.waitForSelector('#details', { waitFor: 'hidden' }); await page.waitForSelector('#details', { state: 'hidden' });
// Wait for #promo to be removed from the DOM. // Wait for #promo to be removed from the DOM.
await page.waitForSelector('#promo', { waitFor: 'detached' }); await page.waitForSelector('#promo', { state: 'detached' });
``` ```
#### API reference #### API reference

View file

@ -434,14 +434,16 @@ export class Frame {
async waitForSelector(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> { async waitForSelector(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
if (options && (options as any).visibility) if (options && (options as any).visibility)
throw new Error('options.visibility is not supported, did you mean options.waitFor?'); throw new Error('options.visibility is not supported, did you mean options.state?');
const { waitFor = 'attached' } = (options || {}); if (options && (options as any).waitFor && (options as any).waitFor !== 'visible')
if (!['attached', 'detached', 'visible', 'hidden'].includes(waitFor)) throw new Error('options.waitFor is not supported, did you mean options.state?');
throw new Error(`Unsupported waitFor option "${waitFor}"`); const { state = 'visible' } = (options || {});
if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
throw new Error(`Unsupported waitFor option "${state}"`);
const deadline = this._page._timeoutSettings.computeDeadline(options); const deadline = this._page._timeoutSettings.computeDeadline(options);
const { world, task } = selectors._waitForSelectorTask(selector, waitFor, deadline); const { world, task } = selectors._waitForSelectorTask(selector, state, deadline);
const result = await this._scheduleRerunnableTask(task, world, deadline, `selector "${selectorToString(selector, waitFor)}"`); const result = await this._scheduleRerunnableTask(task, world, deadline, `selector "${selectorToString(selector, state)}"`);
if (!result.asElement()) { if (!result.asElement()) {
result.dispose(); result.dispose();
return null; return null;
@ -936,9 +938,9 @@ class RerunnableTask {
} }
} }
function selectorToString(selector: string, waitFor: 'attached' | 'detached' | 'visible' | 'hidden'): string { function selectorToString(selector: string, state: 'attached' | 'detached' | 'visible' | 'hidden'): string {
let label; let label;
switch (waitFor) { switch (state) {
case 'visible': label = '[visible] '; break; case 'visible': label = '[visible] '; break;
case 'hidden': label = '[hidden] '; break; case 'hidden': label = '[hidden] '; break;
case 'attached': label = ''; break; case 'attached': label = ''; break;

View file

@ -142,12 +142,12 @@ export class Selectors {
return result; return result;
} }
_waitForSelectorTask(selector: string, waitFor: 'attached' | 'detached' | 'visible' | 'hidden', deadline: number): { world: 'main' | 'utility', task: (context: dom.FrameExecutionContext) => Promise<js.JSHandle> } { _waitForSelectorTask(selector: string, state: 'attached' | 'detached' | 'visible' | 'hidden', deadline: number): { world: 'main' | 'utility', task: (context: dom.FrameExecutionContext) => Promise<js.JSHandle> } {
const parsed = this._parseSelector(selector); const parsed = this._parseSelector(selector);
const task = async (context: dom.FrameExecutionContext) => context.evaluateHandleInternal(({ evaluator, parsed, waitFor, timeout }) => { const task = async (context: dom.FrameExecutionContext) => context.evaluateHandleInternal(({ evaluator, parsed, state, timeout }) => {
return evaluator.injected.poll('raf', timeout, () => { return evaluator.injected.poll('raf', timeout, () => {
const element = evaluator.querySelector(parsed, document); const element = evaluator.querySelector(parsed, document);
switch (waitFor) { switch (state) {
case 'attached': case 'attached':
return element || false; return element || false;
case 'detached': case 'detached':
@ -158,7 +158,7 @@ export class Selectors {
return !element || !evaluator.injected.isVisible(element); return !element || !evaluator.injected.isVisible(element);
} }
}); });
}, { evaluator: await this._prepareEvaluator(context), parsed, waitFor, timeout: helper.timeUntilDeadline(deadline) }); }, { evaluator: await this._prepareEvaluator(context), parsed, state, timeout: helper.timeUntilDeadline(deadline) });
return { world: this._needsMainContext(parsed) ? 'main' : 'utility', task }; return { world: this._needsMainContext(parsed) ? 'main' : 'utility', task };
} }

View file

@ -37,7 +37,7 @@ export type Quad = [ Point, Point, Point, Point ];
export type TimeoutOptions = { timeout?: number }; export type TimeoutOptions = { timeout?: number };
export type WaitForElementOptions = TimeoutOptions & { waitFor?: 'attached' | 'detached' | 'visible' | 'hidden' }; export type WaitForElementOptions = TimeoutOptions & { state?: 'attached' | 'detached' | 'visible' | 'hidden' };
export type Polling = 'raf' | number; export type Polling = 'raf' | number;
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling }; export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };

View file

@ -171,10 +171,10 @@ describe('Browser.disconnect', function() {
const browserServer = await browserType.launchServer(defaultBrowserOptions); const browserServer = await browserType.launchServer(defaultBrowserOptions);
const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage(); const page = await remote.newPage();
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e); const watchdog = page.waitForSelector('div', { state: 'attached', timeout: 60000 }).catch(e => e);
// Make sure the previous waitForSelector has time to make it to the browser before we disconnect. // Make sure the previous waitForSelector has time to make it to the browser before we disconnect.
await page.waitForSelector('body'); await page.waitForSelector('body', { state: 'attached' });
await remote.close(); await remote.close();
const error = await watchdog; const error = await watchdog;

View file

@ -162,12 +162,22 @@ describe('Frame.waitForFunction', function() {
describe('Frame.waitForSelector', function() { describe('Frame.waitForSelector', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag)); const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should throw on waitFor', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let error;
await page.waitForSelector('*', { waitFor: 'attached' }).catch(e => error = e);
expect(error.message).toBe('options.waitFor is not supported, did you mean options.state?');
});
it('should tolerate waitFor=visible', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.waitForSelector('*', { waitFor: 'visible' }).catch(e => error = e);
});
it('should immediately resolve promise if node exists', async({page, server}) => { it('should immediately resolve promise if node exists', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
await frame.waitForSelector('*'); await frame.waitForSelector('*');
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
await frame.waitForSelector('div'); await frame.waitForSelector('div', { state: 'attached'});
}); });
it('should work with removed MutationObserver', async({page, server}) => { it('should work with removed MutationObserver', async({page, server}) => {
await page.evaluate(() => delete window.MutationObserver); await page.evaluate(() => delete window.MutationObserver);
@ -180,7 +190,7 @@ describe('Frame.waitForSelector', function() {
it('should resolve promise when node is added', async({page, server}) => { it('should resolve promise when node is added', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
const watchdog = frame.waitForSelector('div'); const watchdog = frame.waitForSelector('div', { state: 'attached' });
await frame.evaluate(addElement, 'br'); await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
const eHandle = await watchdog; const eHandle = await watchdog;
@ -206,7 +216,7 @@ describe('Frame.waitForSelector', function() {
}); });
it('should work when node is added through innerHTML', async({page, server}) => { it('should work when node is added through innerHTML', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('h3 div'); const watchdog = page.waitForSelector('h3 div', { state: 'attached'});
await page.evaluate(addElement, 'span'); await page.evaluate(addElement, 'span');
await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>'); await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>');
await watchdog; await watchdog;
@ -215,7 +225,7 @@ describe('Frame.waitForSelector', function() {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1]; const otherFrame = page.frames()[1];
const watchdog = page.waitForSelector('div'); const watchdog = page.waitForSelector('div', { state: 'attached' });
await otherFrame.evaluate(addElement, 'div'); await otherFrame.evaluate(addElement, 'div');
await page.evaluate(addElement, 'div'); await page.evaluate(addElement, 'div');
const eHandle = await watchdog; const eHandle = await watchdog;
@ -226,7 +236,7 @@ describe('Frame.waitForSelector', function() {
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1];
const frame2 = page.frames()[2]; const frame2 = page.frames()[2];
const waitForSelectorPromise = frame2.waitForSelector('div'); const waitForSelectorPromise = frame2.waitForSelector('div', { state: 'attached' });
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForSelectorPromise; const eHandle = await waitForSelectorPromise;
@ -255,7 +265,7 @@ describe('Frame.waitForSelector', function() {
}); });
it('should wait for visible', async({page, server}) => { it('should wait for visible', async({page, server}) => {
let divFound = false; let divFound = false;
const waitForSelector = page.waitForSelector('div', { waitFor: 'visible' }).then(() => divFound = true); const waitForSelector = page.waitForSelector('div').then(() => divFound = true);
await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`); await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`);
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
@ -266,17 +276,17 @@ describe('Frame.waitForSelector', function() {
}); });
it('should not consider visible when zero-sized', async({page, server}) => { it('should not consider visible when zero-sized', async({page, server}) => {
await page.setContent(`<div style='width: 0; height: 0;'>1</div>`); await page.setContent(`<div style='width: 0; height: 0;'>1</div>`);
let error = await page.waitForSelector('div', { waitFor: 'visible', timeout: 1000 }).catch(e => e); let error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e);
expect(error.message).toContain('timeout exceeded'); expect(error.message).toContain('timeout exceeded');
await page.evaluate(() => document.querySelector('div').style.width = '10px'); await page.evaluate(() => document.querySelector('div').style.width = '10px');
error = await page.waitForSelector('div', { waitFor: 'visible', timeout: 1000 }).catch(e => e); error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e);
expect(error.message).toContain('timeout exceeded'); expect(error.message).toContain('timeout exceeded');
await page.evaluate(() => document.querySelector('div').style.height = '10px'); await page.evaluate(() => document.querySelector('div').style.height = '10px');
expect(await page.waitForSelector('div', { waitFor: 'visible', timeout: 1000 })).toBeTruthy(); expect(await page.waitForSelector('div', { timeout: 1000 })).toBeTruthy();
}); });
it('should wait for visible recursively', async({page, server}) => { it('should wait for visible recursively', async({page, server}) => {
let divVisible = false; let divVisible = false;
const waitForSelector = page.waitForSelector('div#inner', { waitFor: 'visible' }).then(() => divVisible = true); const waitForSelector = page.waitForSelector('div#inner').then(() => divVisible = true);
await page.setContent(`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`); await page.setContent(`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`);
expect(divVisible).toBe(false); expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
@ -288,7 +298,7 @@ describe('Frame.waitForSelector', function() {
it('hidden should wait for hidden', async({page, server}) => { it('hidden should wait for hidden', async({page, server}) => {
let divHidden = false; let divHidden = false;
await page.setContent(`<div style='display: block;'>content</div>`); await page.setContent(`<div style='display: block;'>content</div>`);
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true); const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden')); await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
@ -298,7 +308,7 @@ describe('Frame.waitForSelector', function() {
it('hidden should wait for display: none', async({page, server}) => { it('hidden should wait for display: none', async({page, server}) => {
let divHidden = false; let divHidden = false;
await page.setContent(`<div style='display: block;'>content</div>`); await page.setContent(`<div style='display: block;'>content</div>`);
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true); const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none')); await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
@ -308,7 +318,7 @@ describe('Frame.waitForSelector', function() {
it('hidden should wait for removal', async({page, server}) => { it('hidden should wait for removal', async({page, server}) => {
await page.setContent(`<div>content</div>`); await page.setContent(`<div>content</div>`);
let divRemoved = false; let divRemoved = false;
const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divRemoved = true); const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divRemoved = true);
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divRemoved).toBe(false); expect(divRemoved).toBe(false);
await page.evaluate(() => document.querySelector('div').remove()); await page.evaluate(() => document.querySelector('div').remove());
@ -316,12 +326,12 @@ describe('Frame.waitForSelector', function() {
expect(divRemoved).toBe(true); expect(divRemoved).toBe(true);
}); });
it('should return null if waiting to hide non-existing element', async({page, server}) => { it('should return null if waiting to hide non-existing element', async({page, server}) => {
const handle = await page.waitForSelector('non-existing', { waitFor: 'hidden' }); const handle = await page.waitForSelector('non-existing', { state: 'hidden' });
expect(handle).toBe(null); expect(handle).toBe(null);
}); });
it('should respect timeout', async({page, server}) => { it('should respect timeout', async({page, server}) => {
let error = null; let error = null;
await page.waitForSelector('div', { timeout: 10 }).catch(e => error = e); await page.waitForSelector('div', { timeout: 10, state: 'attached' }).catch(e => error = e);
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "div" failed: timeout'); expect(error.message).toContain('waiting for selector "div" failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
@ -329,13 +339,13 @@ describe('Frame.waitForSelector', function() {
it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => { it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => {
await page.setContent(`<div>content</div>`); await page.setContent(`<div>content</div>`);
let error = null; let error = null;
await page.waitForSelector('div', { waitFor: 'hidden', timeout: 1000 }).catch(e => error = e); await page.waitForSelector('div', { state: 'hidden', timeout: 1000 }).catch(e => error = e);
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout'); expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
}); });
it('should respond to node attribute mutation', async({page, server}) => { it('should respond to node attribute mutation', async({page, server}) => {
let divFound = false; let divFound = false;
const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true); const waitForSelector = page.waitForSelector('.zombo', { state: 'attached'}).then(() => divFound = true);
await page.setContent(`<div class='notZombo'></div>`); await page.setContent(`<div class='notZombo'></div>`);
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').className = 'zombo'); await page.evaluate(() => document.querySelector('div').className = 'zombo');
@ -353,28 +363,28 @@ describe('Frame.waitForSelector', function() {
}); });
it('should throw for unknown waitFor option', async({page, server}) => { it('should throw for unknown waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: 'foo' }).catch(e => e); const error = await page.waitForSelector('section', { state: 'foo' }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option'); expect(error.message).toContain('Unsupported waitFor option');
}); });
it('should throw for visibility option', async({page, server}) => { it('should throw for visibility option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e); const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e);
expect(error.message).toBe('options.visibility is not supported, did you mean options.waitFor?'); expect(error.message).toBe('options.visibility is not supported, did you mean options.state?');
}); });
it('should throw for true waitFor option', async({page, server}) => { it('should throw for true waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: true }).catch(e => e); const error = await page.waitForSelector('section', { state: true }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option'); expect(error.message).toContain('Unsupported waitFor option');
}); });
it('should throw for false waitFor option', async({page, server}) => { it('should throw for false waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { waitFor: false }).catch(e => e); const error = await page.waitForSelector('section', { state: false }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option'); expect(error.message).toContain('Unsupported waitFor option');
}); });
it('should support >> selector syntax', async({page, server}) => { it('should support >> selector syntax', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
const watchdog = frame.waitForSelector('css=div >> css=span'); const watchdog = frame.waitForSelector('css=div >> css=span', { state: 'attached'});
await frame.evaluate(addElement, 'br'); await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span'))); await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span')));
@ -384,12 +394,12 @@ describe('Frame.waitForSelector', function() {
}); });
it('should wait for detached if already detached', async({page, server}) => { it('should wait for detached if already detached', async({page, server}) => {
await page.setContent('<section id="testAttribute">43543</section>'); await page.setContent('<section id="testAttribute">43543</section>');
expect(await page.waitForSelector('css=div', { waitFor: 'detached'})).toBe(null); expect(await page.waitForSelector('css=div', { state: 'detached'})).toBe(null);
}); });
it('should wait for detached', async({page, server}) => { it('should wait for detached', async({page, server}) => {
await page.setContent('<section id="testAttribute"><div>43543</div></section>'); await page.setContent('<section id="testAttribute"><div>43543</div></section>');
let done = false; let done = false;
const waitFor = page.waitForSelector('css=div', { waitFor: 'detached'}).then(() => done = true); const waitFor = page.waitForSelector('css=div', { state: 'detached'}).then(() => done = true);
expect(done).toBe(false); expect(done).toBe(false);
await page.waitForSelector('css=section'); await page.waitForSelector('css=section');
expect(done).toBe(false); expect(done).toBe(false);
@ -409,7 +419,7 @@ describe('Frame.waitForSelector xpath', function() {
}); });
it('should respect timeout', async({page}) => { it('should respect timeout', async({page}) => {
let error = null; let error = null;
await page.waitForSelector('//div', { timeout: 10 }).catch(e => error = e); await page.waitForSelector('//div', { state: 'attached', timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "//div" failed: timeout'); expect(error.message).toContain('waiting for selector "//div" failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
@ -419,7 +429,7 @@ describe('Frame.waitForSelector xpath', function() {
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1];
const frame2 = page.frames()[2]; const frame2 = page.frames()[2];
const waitForXPathPromise = frame2.waitForSelector('//div'); const waitForXPathPromise = frame2.waitForSelector('//div', { state: 'attached' });
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForXPathPromise; const eHandle = await waitForXPathPromise;

View file

@ -39,7 +39,7 @@ type HTMLOrSVGElement = SVGElement | HTMLElement;
type HTMLOrSVGElementHandle = ElementHandle<HTMLOrSVGElement>; type HTMLOrSVGElementHandle = ElementHandle<HTMLOrSVGElement>;
type WaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & { type WaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & {
waitFor: 'visible'|'attached'; state: 'visible'|'attached';
} }
export interface Page { export interface Page {

View file

@ -155,7 +155,7 @@ playwright.chromium.launch().then(async browser => {
let currentURL: string; let currentURL: string;
page page
.waitForSelector('img', { waitFor: 'visible' }) .waitForSelector('img')
.then(() => console.log('First URL with image: ' + currentURL)); .then(() => console.log('First URL with image: ' + currentURL));
for (currentURL of [ for (currentURL of [
'https://example.com', 'https://example.com',
@ -594,7 +594,7 @@ playwright.chromium.launch().then(async browser => {
}, 5); }, 5);
const something = Math.random() > .5 ? 'visible' : 'attached'; const something = Math.random() > .5 ? 'visible' : 'attached';
const handle = await page.waitForSelector('a', {waitFor: something}); const handle = await page.waitForSelector('a', {state: something});
await handle.$eval('span', (element, { x, y }) => { await handle.$eval('span', (element, { x, y }) => {
const spanAssertion: AssertType<HTMLSpanElement, typeof element> = true; const spanAssertion: AssertType<HTMLSpanElement, typeof element> = true;
const numberAssertion: AssertType<number, typeof x> = true; const numberAssertion: AssertType<number, typeof x> = true;
@ -654,19 +654,19 @@ playwright.chromium.launch().then(async browser => {
const canBeNull: AssertType<null, typeof handle> = false; const canBeNull: AssertType<null, typeof handle> = false;
} }
{ {
const waitFor = Math.random() > .5 ? 'attached' : 'visible'; const state = Math.random() > .5 ? 'attached' : 'visible';
const handle = await frameLike.waitForSelector('body', {waitFor}); const handle = await frameLike.waitForSelector('body', {state});
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true; const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = false; const canBeNull: AssertType<null, typeof handle> = false;
} }
{ {
const handle = await frameLike.waitForSelector('body', {waitFor: 'hidden'}); const handle = await frameLike.waitForSelector('body', {state: 'hidden'});
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true; const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = true; const canBeNull: AssertType<null, typeof handle> = true;
} }
{ {
const waitFor = Math.random() > .5 ? 'hidden' : 'visible'; const state = Math.random() > .5 ? 'hidden' : 'visible';
const handle = await frameLike.waitForSelector('body', {waitFor}); const handle = await frameLike.waitForSelector('body', {state});
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true; const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = true; const canBeNull: AssertType<null, typeof handle> = true;
} }
@ -677,14 +677,14 @@ playwright.chromium.launch().then(async browser => {
const canBeNull: AssertType<null, typeof handle> = false; const canBeNull: AssertType<null, typeof handle> = false;
} }
{ {
const waitFor = Math.random() > .5 ? 'attached' : 'visible'; const state = Math.random() > .5 ? 'attached' : 'visible';
const handle = await frameLike.waitForSelector('something-strange', {waitFor}); const handle = await frameLike.waitForSelector('something-strange', {state});
const elementAssertion: AssertType<playwright.ElementHandle<HTMLElement|SVGElement>, typeof handle> = true; const elementAssertion: AssertType<playwright.ElementHandle<HTMLElement|SVGElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = false; const canBeNull: AssertType<null, typeof handle> = false;
} }
{ {
const waitFor = Math.random() > .5 ? 'hidden' : 'visible'; const state = Math.random() > .5 ? 'hidden' : 'visible';
const handle = await frameLike.waitForSelector('something-strange', {waitFor}); const handle = await frameLike.waitForSelector('something-strange', {state});
const elementAssertion: AssertType<playwright.ElementHandle<HTMLElement|SVGElement>, typeof handle> = true; const elementAssertion: AssertType<playwright.ElementHandle<HTMLElement|SVGElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = true; const canBeNull: AssertType<null, typeof handle> = true;
} }