feature: update api around selectors (#280)
- Selector is again a string.
- Most methods taking selector also accept waitFor option.
- Available waitFor options are: 'visible', 'hidden', 'any' === true, false === undefined.
- waitForXPath is removed.
- waitForSelector is replaced by $(selector, { waitFor: true }).
This commit is contained in:
parent
75ae9bfeee
commit
fd927000ea
117
src/dom.ts
117
src/dom.ts
|
|
@ -11,9 +11,6 @@ import { assert, helper, debugError } from './helper';
|
|||
import Injected from './injected/injected';
|
||||
import { Page } from './page';
|
||||
|
||||
type ScopedSelector = types.Selector & { scope?: ElementHandle };
|
||||
type ResolvedSelector = { scope?: ElementHandle, selector: string, visibility: types.Visibility, disposeScope?: boolean };
|
||||
|
||||
export class FrameExecutionContext extends js.ExecutionContext {
|
||||
private readonly _frame: frames.Frame;
|
||||
|
||||
|
|
@ -47,52 +44,26 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
|||
return this._injectedPromise;
|
||||
}
|
||||
|
||||
async _adoptElementHandle<T extends Node>(handle: ElementHandle<T>): Promise<ElementHandle<T>> {
|
||||
assert(handle.executionContext() !== this, 'Should not adopt to the same context');
|
||||
return this._frame._page._delegate.adoptElementHandle(handle, this);
|
||||
}
|
||||
|
||||
async _resolveSelector(selector: string | ScopedSelector): Promise<ResolvedSelector> {
|
||||
if (helper.isString(selector))
|
||||
return { selector: normalizeSelector(selector), visibility: 'any' };
|
||||
if (selector.scope && selector.scope.executionContext() !== this) {
|
||||
const scope = await this._adoptElementHandle(selector.scope);
|
||||
return { scope, selector: normalizeSelector(selector.selector), disposeScope: true, visibility: selector.visibility || 'any' };
|
||||
}
|
||||
return { scope: selector.scope, selector: normalizeSelector(selector.selector), visibility: selector.visibility || 'any' };
|
||||
}
|
||||
|
||||
async _$(selector: string | ScopedSelector): Promise<ElementHandle<Element> | null> {
|
||||
const resolved = await this._resolveSelector(selector);
|
||||
async _$(selector: string, scope?: ElementHandle): Promise<ElementHandle<Element> | null> {
|
||||
const handle = await this.evaluateHandle(
|
||||
(injected: Injected, selector: string, visibility: types.Visibility, scope?: Node) => {
|
||||
const element = injected.querySelector(selector, scope || document);
|
||||
if (visibility === 'any' || !element)
|
||||
return element;
|
||||
return injected.isVisible(element) === (visibility === 'visible') ? element : undefined;
|
||||
},
|
||||
await this._injected(), resolved.selector, resolved.visibility, resolved.scope
|
||||
(injected: Injected, selector: string, scope?: Node) => injected.querySelector(selector, scope || document),
|
||||
await this._injected(), normalizeSelector(selector), scope
|
||||
);
|
||||
if (resolved.disposeScope)
|
||||
await resolved.scope.dispose();
|
||||
if (!handle.asElement())
|
||||
await handle.dispose();
|
||||
return handle.asElement();
|
||||
}
|
||||
|
||||
async _$$(selector: string | ScopedSelector): Promise<ElementHandle<Element>[]> {
|
||||
const resolved = await this._resolveSelector(selector);
|
||||
async _$array(selector: string, scope?: ElementHandle): Promise<js.JSHandle<Element[]>> {
|
||||
const arrayHandle = await this.evaluateHandle(
|
||||
(injected: Injected, selector: string, visibility: types.Visibility, scope?: Node) => {
|
||||
const elements = injected.querySelectorAll(selector, scope || document);
|
||||
if (visibility !== 'any')
|
||||
return elements.filter(element => injected.isVisible(element) === (visibility === 'visible'));
|
||||
return elements;
|
||||
},
|
||||
await this._injected(), resolved.selector, resolved.visibility, resolved.scope
|
||||
(injected: Injected, selector: string, scope?: Node) => injected.querySelectorAll(selector, scope || document),
|
||||
await this._injected(), normalizeSelector(selector), scope
|
||||
);
|
||||
if (resolved.disposeScope)
|
||||
await resolved.scope.dispose();
|
||||
return arrayHandle;
|
||||
}
|
||||
|
||||
async _$$(selector: string, scope?: ElementHandle): Promise<ElementHandle<Element>[]> {
|
||||
const arrayHandle = await this._$array(selector, scope);
|
||||
const properties = await arrayHandle.getProperties();
|
||||
await arrayHandle.dispose();
|
||||
const result = [];
|
||||
|
|
@ -105,31 +76,6 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_$eval: types.$Eval<string | ScopedSelector> = async (selector, pageFunction, ...args) => {
|
||||
const elementHandle = await this._$(selector);
|
||||
if (!elementHandle)
|
||||
throw new Error(`Error: failed to find element matching selector "${types.selectorToString(selector)}"`);
|
||||
const result = await elementHandle.evaluate(pageFunction, ...args as any);
|
||||
await elementHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
_$$eval: types.$$Eval<string | ScopedSelector> = async (selector, pageFunction, ...args) => {
|
||||
const resolved = await this._resolveSelector(selector);
|
||||
const arrayHandle = await this.evaluateHandle(
|
||||
(injected: Injected, selector: string, visibility: types.Visibility, scope?: Node) => {
|
||||
const elements = injected.querySelectorAll(selector, scope || document);
|
||||
if (visibility !== 'any')
|
||||
return elements.filter(element => injected.isVisible(element) === (visibility === 'visible'));
|
||||
return elements;
|
||||
},
|
||||
await this._injected(), resolved.selector, resolved.visibility, resolved.scope
|
||||
);
|
||||
const result = await arrayHandle.evaluate(pageFunction, ...args as any);
|
||||
await arrayHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
|
|
@ -348,7 +294,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
async type(text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
async type(text: string, options?: { delay?: number }) {
|
||||
await this.focus();
|
||||
await this._page.keyboard.type(text, options);
|
||||
}
|
||||
|
|
@ -366,31 +312,32 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
return this._page._screenshotter.screenshotElement(this, options);
|
||||
}
|
||||
|
||||
private _scopedSelector(selector: string | types.Selector): string | ScopedSelector {
|
||||
selector = types.clearSelector(selector);
|
||||
if (helper.isString(selector))
|
||||
selector = { selector };
|
||||
return { scope: this, selector: selector.selector, visibility: selector.visibility };
|
||||
$(selector: string): Promise<ElementHandle | null> {
|
||||
return this._context._$(selector, this);
|
||||
}
|
||||
|
||||
$(selector: string | types.Selector): Promise<ElementHandle | null> {
|
||||
return this._context._$(this._scopedSelector(selector));
|
||||
$$(selector: string): Promise<ElementHandle<Element>[]> {
|
||||
return this._context._$$(selector, this);
|
||||
}
|
||||
|
||||
$$(selector: string | types.Selector): Promise<ElementHandle<Element>[]> {
|
||||
return this._context._$$(this._scopedSelector(selector));
|
||||
$eval: types.$Eval = async (selector, pageFunction, ...args) => {
|
||||
const elementHandle = await this._context._$(selector, this);
|
||||
if (!elementHandle)
|
||||
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
||||
const result = await elementHandle.evaluate(pageFunction, ...args as any);
|
||||
await elementHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
$eval: types.$Eval<string | types.Selector> = (selector, pageFunction, ...args) => {
|
||||
return this._context._$eval(this._scopedSelector(selector), pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
$$eval: types.$$Eval<string | types.Selector> = (selector, pageFunction, ...args) => {
|
||||
return this._context._$$eval(this._scopedSelector(selector), pageFunction, ...args as any);
|
||||
$$eval: types.$$Eval = async (selector, pageFunction, ...args) => {
|
||||
const arrayHandle = await this._context._$array(selector, this);
|
||||
const result = await arrayHandle.evaluate(pageFunction, ...args as any);
|
||||
await arrayHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
$x(expression: string): Promise<ElementHandle<Element>[]> {
|
||||
return this._context._$$({ scope: this, selector: 'xpath=' + expression });
|
||||
return this._context._$$('xpath=' + expression, this);
|
||||
}
|
||||
|
||||
isIntersectingViewport(): Promise<boolean> {
|
||||
|
|
@ -446,9 +393,9 @@ export function waitForFunctionTask(pageFunction: Function | string, options: ty
|
|||
}, await context._injected(), predicateBody, polling, options.timeout, ...args);
|
||||
}
|
||||
|
||||
export function waitForSelectorTask(selector: string | types.Selector, timeout: number): Task {
|
||||
export function waitForSelectorTask(selector: string, visibility: types.Visibility | undefined, timeout: number): Task {
|
||||
return async (context: FrameExecutionContext) => {
|
||||
const resolved = await context._resolveSelector(selector);
|
||||
selector = normalizeSelector(selector);
|
||||
return context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number, scope?: Node) => {
|
||||
if (visibility !== 'any')
|
||||
return injected.pollRaf(predicate, timeout);
|
||||
|
|
@ -462,6 +409,6 @@ export function waitForSelectorTask(selector: string | types.Selector, timeout:
|
|||
return element;
|
||||
return injected.isVisible(element) === (visibility === 'visible') ? element : false;
|
||||
}
|
||||
}, await context._injected(), resolved.selector, resolved.visibility, timeout, resolved.scope);
|
||||
}, await context._injected(), selector, visibility, timeout, undefined);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
145
src/frames.ts
145
src/frames.ts
|
|
@ -53,7 +53,7 @@ export type GotoResult = {
|
|||
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
|
||||
const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']);
|
||||
|
||||
export type WaitForOptions = types.TimeoutOptions & { waitFor?: boolean };
|
||||
export type WaitForOptions = types.TimeoutOptions & { waitFor?: boolean | 'visible' | 'hidden' | 'any' };
|
||||
|
||||
export class FrameManager {
|
||||
private _page: Page;
|
||||
|
|
@ -340,16 +340,18 @@ export class Frame {
|
|||
return watcher.navigationResponse();
|
||||
}
|
||||
|
||||
_mainContext(): Promise<dom.FrameExecutionContext> {
|
||||
_context(contextType: ContextType): Promise<dom.FrameExecutionContext> {
|
||||
if (this._detached)
|
||||
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
|
||||
return this._contextData.get('main').contextPromise;
|
||||
return this._contextData.get(contextType).contextPromise;
|
||||
}
|
||||
|
||||
_mainContext(): Promise<dom.FrameExecutionContext> {
|
||||
return this._context('main');
|
||||
}
|
||||
|
||||
_utilityContext(): Promise<dom.FrameExecutionContext> {
|
||||
if (this._detached)
|
||||
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
|
||||
return this._contextData.get('utility').contextPromise;
|
||||
return this._context('utility');
|
||||
}
|
||||
|
||||
executionContext(): Promise<dom.FrameExecutionContext> {
|
||||
|
|
@ -366,9 +368,8 @@ export class Frame {
|
|||
return context.evaluate(pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
async $(selector: string | types.Selector): Promise<dom.ElementHandle<Element> | null> {
|
||||
const context = await this._mainContext();
|
||||
return context._$(types.clearSelector(selector));
|
||||
async $(selector: string, options?: WaitForOptions): Promise<dom.ElementHandle<Element> | null> {
|
||||
return this._optionallyWaitForSelector('main', selector, options, true /* returnNull */);
|
||||
}
|
||||
|
||||
async $x(expression: string): Promise<dom.ElementHandle<Element>[]> {
|
||||
|
|
@ -378,17 +379,25 @@ export class Frame {
|
|||
|
||||
$eval: types.$Eval = async (selector, pageFunction, ...args) => {
|
||||
const context = await this._mainContext();
|
||||
return context._$eval(selector, pageFunction, ...args as any);
|
||||
const elementHandle = await context._$(selector);
|
||||
if (!elementHandle)
|
||||
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
||||
const result = await elementHandle.evaluate(pageFunction, ...args as any);
|
||||
await elementHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
$$eval: types.$$Eval = async (selector, pageFunction, ...args) => {
|
||||
const context = await this._mainContext();
|
||||
return context._$$eval(selector, pageFunction, ...args as any);
|
||||
const arrayHandle = await context._$array(selector);
|
||||
const result = await arrayHandle.evaluate(pageFunction, ...args as any);
|
||||
await arrayHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
async $$(selector: string | types.Selector): Promise<dom.ElementHandle<Element>[]> {
|
||||
async $$(selector: string): Promise<dom.ElementHandle<Element>[]> {
|
||||
const context = await this._mainContext();
|
||||
return context._$$(types.clearSelector(selector));
|
||||
return context._$$(selector);
|
||||
}
|
||||
|
||||
async content(): Promise<string> {
|
||||
|
|
@ -581,50 +590,50 @@ export class Frame {
|
|||
return result;
|
||||
}
|
||||
|
||||
async click(selector: string | types.Selector, options?: WaitForOptions & ClickOptions) {
|
||||
const handle = await this._optionallyWaitForInUtilityContext(selector, options);
|
||||
async click(selector: string, options?: WaitForOptions & ClickOptions) {
|
||||
const handle = await this._optionallyWaitForSelector('utility', selector, options);
|
||||
await handle.click(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async dblclick(selector: string | types.Selector, options?: WaitForOptions & MultiClickOptions) {
|
||||
const handle = await this._optionallyWaitForInUtilityContext(selector, options);
|
||||
async dblclick(selector: string, options?: WaitForOptions & MultiClickOptions) {
|
||||
const handle = await this._optionallyWaitForSelector('utility', selector, options);
|
||||
await handle.dblclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async tripleclick(selector: string | types.Selector, options?: WaitForOptions & MultiClickOptions) {
|
||||
const handle = await this._optionallyWaitForInUtilityContext(selector, options);
|
||||
async tripleclick(selector: string, options?: WaitForOptions & MultiClickOptions) {
|
||||
const handle = await this._optionallyWaitForSelector('utility', selector, options);
|
||||
await handle.tripleclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async fill(selector: string | types.Selector, value: string, options?: WaitForOptions) {
|
||||
const handle = await this._optionallyWaitForInUtilityContext(selector, options);
|
||||
async fill(selector: string, value: string, options?: WaitForOptions) {
|
||||
const handle = await this._optionallyWaitForSelector('utility', selector, options);
|
||||
await handle.fill(value);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async focus(selector: string | types.Selector, options?: WaitForOptions) {
|
||||
const handle = await this._optionallyWaitForInUtilityContext(selector, options);
|
||||
async focus(selector: string, options?: WaitForOptions) {
|
||||
const handle = await this._optionallyWaitForSelector('utility', selector, options);
|
||||
await handle.focus();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async hover(selector: string | types.Selector, options?: WaitForOptions & PointerActionOptions) {
|
||||
const handle = await this._optionallyWaitForInUtilityContext(selector, options);
|
||||
async hover(selector: string, options?: WaitForOptions & PointerActionOptions) {
|
||||
const handle = await this._optionallyWaitForSelector('utility', selector, options);
|
||||
await handle.hover(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async select(selector: string | types.Selector, value: string | dom.ElementHandle | SelectOption | string[] | dom.ElementHandle[] | SelectOption[] | undefined, options?: WaitForOptions): Promise<string[]> {
|
||||
const handle = await this._optionallyWaitForInUtilityContext(selector, options);
|
||||
async select(selector: string, value: string | dom.ElementHandle | SelectOption | string[] | dom.ElementHandle[] | SelectOption[] | undefined, options?: WaitForOptions): Promise<string[]> {
|
||||
const handle = await this._optionallyWaitForSelector('utility', selector, options);
|
||||
const toDispose: Promise<dom.ElementHandle>[] = [];
|
||||
const values = value === undefined ? [] : value instanceof Array ? value : [value];
|
||||
const values = value === undefined ? [] : Array.isArray(value) ? value : [value];
|
||||
const context = await this._utilityContext();
|
||||
const adoptedValues = await Promise.all(values.map(async value => {
|
||||
if (value instanceof dom.ElementHandle && value.executionContext() !== context) {
|
||||
const adopted = context._adoptElementHandle(value);
|
||||
const adopted = this._page._delegate.adoptElementHandle(value, context);
|
||||
toDispose.push(adopted);
|
||||
return adopted;
|
||||
}
|
||||
|
|
@ -636,17 +645,15 @@ export class Frame {
|
|||
return result;
|
||||
}
|
||||
|
||||
async type(selector: string | types.Selector, text: string, options: WaitForOptions & { delay: (number | undefined); } | undefined) {
|
||||
const context = await this._utilityContext();
|
||||
const handle = await context._$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
async type(selector: string, text: string, options?: WaitForOptions & { delay?: number }) {
|
||||
const handle = await this._optionallyWaitForSelector('utility', selector, options);
|
||||
await handle.type(text, options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: any = {}, ...args: any[]): Promise<js.JSHandle | null> {
|
||||
if (helper.isString(selectorOrFunctionOrTimeout))
|
||||
return this.waitForSelector(selectorOrFunctionOrTimeout as string, options) as any;
|
||||
return this.$(selectorOrFunctionOrTimeout as string, { waitFor: true, ...options }) as any;
|
||||
if (helper.isNumber(selectorOrFunctionOrTimeout))
|
||||
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout as number));
|
||||
if (typeof selectorOrFunctionOrTimeout === 'function')
|
||||
|
|
@ -654,45 +661,35 @@ export class Frame {
|
|||
return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
|
||||
}
|
||||
|
||||
private async _optionallyWaitForInUtilityContext(selector: string | types.Selector,options: WaitForOptions): Promise<dom.ElementHandle | null> {
|
||||
let handle: dom.ElementHandle | null;
|
||||
if (options && options.waitFor) {
|
||||
handle = await this._waitForSelectorInUtilityContext(selector, options);
|
||||
private async _optionallyWaitForSelector(contextType: ContextType, selector: string, options: WaitForOptions = {}, returnNull?: boolean): Promise<dom.ElementHandle<Element> | null> {
|
||||
const { timeout = this._page._timeoutSettings.timeout(), waitFor = undefined } = options;
|
||||
let handle: dom.ElementHandle<Element> | null;
|
||||
if (waitFor) {
|
||||
let visibility: types.Visibility = 'any';
|
||||
if (waitFor === 'visible' || waitFor === 'hidden' || waitFor === 'any')
|
||||
visibility = waitFor;
|
||||
else if (waitFor === true)
|
||||
visibility = 'any';
|
||||
else
|
||||
throw new Error(`Unsupported waitFor option "${waitFor}"`);
|
||||
const task = dom.waitForSelectorTask(selector, visibility, timeout);
|
||||
const result = await this._scheduleRerunnableTask(task, contextType, timeout, `selector "${selectorToString(selector, visibility)}"`);
|
||||
if (!result.asElement()) {
|
||||
await result.dispose();
|
||||
if (returnNull)
|
||||
return null;
|
||||
throw new Error('No node found for selector: ' + selectorToString(selector, visibility));
|
||||
}
|
||||
handle = result.asElement() as dom.ElementHandle<Element>;
|
||||
} else {
|
||||
const context = await this._utilityContext();
|
||||
handle = await context._$(types.clearSelector(selector));
|
||||
const context = await this._context(contextType);
|
||||
handle = await context._$(selector);
|
||||
if (!returnNull)
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
}
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
return handle;
|
||||
}
|
||||
|
||||
private async _waitForSelectorInUtilityContext(selector: string | types.Selector, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
|
||||
const { timeout = this._page._timeoutSettings.timeout() } = options;
|
||||
const task = dom.waitForSelectorTask(types.clearSelector(selector), timeout);
|
||||
const handle = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${types.selectorToString(selector)}"`);
|
||||
if (!handle.asElement()) {
|
||||
await handle.dispose();
|
||||
return null;
|
||||
}
|
||||
return handle.asElement();
|
||||
}
|
||||
|
||||
async waitForSelector(selector: string | types.Selector, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
|
||||
const handle = await this._waitForSelectorInUtilityContext(selector, options);
|
||||
if (!handle)
|
||||
return null;
|
||||
const maincontext = await this._mainContext();
|
||||
if (handle.executionContext() === maincontext)
|
||||
return handle.asElement();
|
||||
const adopted = await maincontext._adoptElementHandle(handle.asElement());
|
||||
await handle.dispose();
|
||||
return adopted;
|
||||
}
|
||||
|
||||
async waitForXPath(xpath: string, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
|
||||
return this.waitForSelector('xpath=' + xpath, options);
|
||||
}
|
||||
|
||||
waitForFunction(pageFunction: Function | string, options: types.WaitForFunctionOptions = {}, ...args: any[]): Promise<js.JSHandle> {
|
||||
options = { timeout: this._page._timeoutSettings.timeout(), ...options };
|
||||
const task = dom.waitForFunctionTask(pageFunction, options, ...args);
|
||||
|
|
@ -969,3 +966,15 @@ class LifecycleWatcher {
|
|||
clearTimeout(this._maximumTimer);
|
||||
}
|
||||
}
|
||||
|
||||
function selectorToString(selector: string, visibility: types.Visibility): string {
|
||||
let label;
|
||||
switch (visibility) {
|
||||
case 'visible': label = '[visible] '; break;
|
||||
case 'hidden': label = '[hidden] '; break;
|
||||
case 'any':
|
||||
case undefined:
|
||||
label = ''; break;
|
||||
}
|
||||
return `${label}${selector}`;
|
||||
}
|
||||
|
|
@ -133,7 +133,7 @@ export class Keyboard {
|
|||
await this._raw.sendText(text);
|
||||
}
|
||||
|
||||
async type(text: string, options: { delay: (number | undefined); } | undefined) {
|
||||
async type(text: string, options?: { delay?: number }) {
|
||||
const delay = (options && options.delay) || null;
|
||||
for (const char of text) {
|
||||
if (keyboardLayout.keyDefinitions[char]) {
|
||||
|
|
|
|||
30
src/page.ts
30
src/page.ts
|
|
@ -186,8 +186,8 @@ export class Page extends EventEmitter {
|
|||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
async $(selector: string | types.Selector): Promise<dom.ElementHandle<Element> | null> {
|
||||
return this.mainFrame().$(selector);
|
||||
async $(selector: string, options?: frames.WaitForOptions): Promise<dom.ElementHandle<Element> | null> {
|
||||
return this.mainFrame().$(selector, options);
|
||||
}
|
||||
|
||||
async _createSelector(name: string, handle: dom.ElementHandle<Element>): Promise<string> {
|
||||
|
|
@ -210,7 +210,7 @@ export class Page extends EventEmitter {
|
|||
return this.mainFrame().$$eval(selector, pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
async $$(selector: string | types.Selector): Promise<dom.ElementHandle<Element>[]> {
|
||||
async $$(selector: string): Promise<dom.ElementHandle<Element>[]> {
|
||||
return this.mainFrame().$$(selector);
|
||||
}
|
||||
|
||||
|
|
@ -458,35 +458,35 @@ export class Page extends EventEmitter {
|
|||
return this._closed;
|
||||
}
|
||||
|
||||
click(selector: string | types.Selector, options?: frames.WaitForOptions & input.ClickOptions) {
|
||||
click(selector: string, options?: frames.WaitForOptions & input.ClickOptions) {
|
||||
return this.mainFrame().click(selector, options);
|
||||
}
|
||||
|
||||
dblclick(selector: string | types.Selector, options?: frames.WaitForOptions & input.MultiClickOptions) {
|
||||
dblclick(selector: string, options?: frames.WaitForOptions & input.MultiClickOptions) {
|
||||
return this.mainFrame().dblclick(selector, options);
|
||||
}
|
||||
|
||||
tripleclick(selector: string | types.Selector, options?: frames.WaitForOptions & input.MultiClickOptions) {
|
||||
tripleclick(selector: string, options?: frames.WaitForOptions & input.MultiClickOptions) {
|
||||
return this.mainFrame().tripleclick(selector, options);
|
||||
}
|
||||
|
||||
fill(selector: string | types.Selector, value: string, options?: frames.WaitForOptions) {
|
||||
fill(selector: string, value: string, options?: frames.WaitForOptions) {
|
||||
return this.mainFrame().fill(selector, value, options);
|
||||
}
|
||||
|
||||
focus(selector: string | types.Selector, options?: frames.WaitForOptions) {
|
||||
focus(selector: string, options?: frames.WaitForOptions) {
|
||||
return this.mainFrame().focus(selector, options);
|
||||
}
|
||||
|
||||
hover(selector: string | types.Selector, options?: frames.WaitForOptions & input.PointerActionOptions) {
|
||||
hover(selector: string, options?: frames.WaitForOptions & input.PointerActionOptions) {
|
||||
return this.mainFrame().hover(selector, options);
|
||||
}
|
||||
|
||||
select(selector: string | types.Selector, value: string | dom.ElementHandle | input.SelectOption | string[] | dom.ElementHandle[] | input.SelectOption[] | undefined, options?: frames.WaitForOptions): Promise<string[]> {
|
||||
select(selector: string, value: string | dom.ElementHandle | input.SelectOption | string[] | dom.ElementHandle[] | input.SelectOption[] | undefined, options?: frames.WaitForOptions): Promise<string[]> {
|
||||
return this.mainFrame().select(selector, value, options);
|
||||
}
|
||||
|
||||
type(selector: string | types.Selector, text: string, options: frames.WaitForOptions & { delay: (number | undefined); } | undefined) {
|
||||
type(selector: string, text: string, options?: frames.WaitForOptions & { delay?: number }) {
|
||||
return this.mainFrame().type(selector, text, options);
|
||||
}
|
||||
|
||||
|
|
@ -494,14 +494,6 @@ export class Page extends EventEmitter {
|
|||
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
|
||||
}
|
||||
|
||||
waitForSelector(selector: string | types.Selector, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
|
||||
return this.mainFrame().waitForSelector(selector, options);
|
||||
}
|
||||
|
||||
waitForXPath(xpath: string, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
|
||||
return this.mainFrame().waitForXPath(xpath, options);
|
||||
}
|
||||
|
||||
waitForFunction(pageFunction: Function | string, options: types.WaitForFunctionOptions, ...args: any[]): Promise<js.JSHandle> {
|
||||
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
|
||||
}
|
||||
|
|
|
|||
27
src/types.ts
27
src/types.ts
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
import * as js from './javascript';
|
||||
import { helper } from './helper';
|
||||
import * as dom from './dom';
|
||||
|
||||
type Boxed<Args extends any[]> = { [Index in keyof Args]: Args[Index] | js.JSHandle<Args[Index]> };
|
||||
|
|
@ -14,8 +13,8 @@ type ElementForSelector<T> = T extends keyof HTMLElementTagNameMap ? HTMLElement
|
|||
|
||||
export type Evaluate = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type EvaluateHandle = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;
|
||||
export type $Eval<O = string | Selector> = <Args extends any[], R, S extends O>(selector: S, pageFunction: PageFunctionOn<ElementForSelector<S>, Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type $$Eval<O = string | Selector> = <Args extends any[], R, S extends O>(selector: S, pageFunction: PageFunctionOn<ElementForSelector<S>[], Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type $Eval = <Args extends any[], R, S extends string>(selector: S, pageFunction: PageFunctionOn<ElementForSelector<S>, Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type $$Eval = <Args extends any[], R, S extends string>(selector: S, pageFunction: PageFunctionOn<ElementForSelector<S>[], Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type EvaluateOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<R>;
|
||||
export type EvaluateHandleOn<T> = <Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>) => Promise<Handle<R>>;
|
||||
|
||||
|
|
@ -26,32 +25,10 @@ export type Quad = [ Point, Point, Point, Point ];
|
|||
|
||||
export type TimeoutOptions = { timeout?: number };
|
||||
export type Visibility = 'visible' | 'hidden' | 'any';
|
||||
export type Selector = { selector: string, visibility?: Visibility };
|
||||
|
||||
export type Polling = 'raf' | 'mutation' | number;
|
||||
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };
|
||||
|
||||
export function selectorToString(selector: string | Selector): string {
|
||||
if (typeof selector === 'string')
|
||||
return selector;
|
||||
let label;
|
||||
switch (selector.visibility) {
|
||||
case 'visible': label = '[visible] '; break;
|
||||
case 'hidden': label = '[hidden] '; break;
|
||||
case 'any':
|
||||
case undefined:
|
||||
label = ''; break;
|
||||
}
|
||||
return `${label}${selector.selector}`;
|
||||
}
|
||||
|
||||
// Ensures that we don't use accidental properties in selector, e.g. scope.
|
||||
export function clearSelector(selector: string | Selector): string | Selector {
|
||||
if (helper.isString(selector))
|
||||
return selector;
|
||||
return { selector: selector.selector, visibility: selector.visibility };
|
||||
}
|
||||
|
||||
export type ElementScreenshotOptions = {
|
||||
type?: 'png' | 'jpeg',
|
||||
path?: string,
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
|
|||
const browser = await playwright.launch(defaultBrowserOptions);
|
||||
const remote = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint: browser.chromium.wsEndpoint()});
|
||||
const page = await remote.newPage();
|
||||
const watchdog = page.waitForSelector('div', {timeout: 60000}).catch(e => e);
|
||||
const watchdog = page.$('div', { waitFor: true, timeout: 60000 }).catch(e => e);
|
||||
remote.disconnect();
|
||||
const error = await watchdog;
|
||||
expect(error.message).toContain('Protocol error');
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ module.exports.addTests = function({testRunner, expect, playwright, defaultBrows
|
|||
document.body.appendChild(frame);
|
||||
return new Promise(x => frame.onload = x);
|
||||
});
|
||||
await page.waitForSelector('iframe[src="https://google.com/"]');
|
||||
await page.$('iframe[src="https://google.com/"]', { waitFor: true });
|
||||
const urls = page.frames().map(frame => frame.url()).sort();
|
||||
expect(urls).toEqual([
|
||||
server.EMPTY_PAGE,
|
||||
|
|
|
|||
|
|
@ -126,31 +126,44 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
|
|||
]);
|
||||
});
|
||||
|
||||
it('should respect selector visibilty', async({page, server}) => {
|
||||
it('should waitFor visible when already visible', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.click({selector: 'button', visibility: 'visible'});
|
||||
await page.click('button', { waitFor: 'visible' });
|
||||
expect(await page.evaluate(() => result)).toBe('Clicked');
|
||||
|
||||
});
|
||||
it('should waitFor hidden when already hidden', async({page, server}) => {
|
||||
let error = null;
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.click({selector: 'button', visibility: 'hidden'}).catch(e => error = e);
|
||||
expect(error.message).toBe('No node found for selector: [hidden] button');
|
||||
expect(await page.evaluate(() => result)).toBe('Was not clicked');
|
||||
|
||||
error = null;
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.$eval('button', b => b.style.display = 'none');
|
||||
await page.click({selector: 'button', visibility: 'visible'}).catch(e => error = e);
|
||||
expect(error.message).toBe('No node found for selector: [visible] button');
|
||||
expect(await page.evaluate(() => result)).toBe('Was not clicked');
|
||||
|
||||
error = null;
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.$eval('button', b => b.style.display = 'none');
|
||||
await page.click({selector: 'button', visibility: 'hidden'}).catch(e => error = e);
|
||||
await page.click('button', { waitFor: 'hidden' }).catch(e => error = e);
|
||||
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
|
||||
expect(await page.evaluate(() => result)).toBe('Was not clicked');
|
||||
});
|
||||
it('should waitFor hidden', async({page, server}) => {
|
||||
let error = null;
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const clicked = page.click('button', { waitFor: 'hidden' }).catch(e => error = e);
|
||||
for (let i = 0; i < 5; i++)
|
||||
await page.evaluate('1'); // Do a round trip.
|
||||
expect(error).toBe(null);
|
||||
await page.$eval('button', b => b.style.display = 'none');
|
||||
await clicked;
|
||||
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
|
||||
expect(await page.evaluate(() => result)).toBe('Was not clicked');
|
||||
});
|
||||
it('should waitFor visible', async({page, server}) => {
|
||||
let done = false;
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.$eval('button', b => b.style.display = 'none');
|
||||
const clicked = page.click('button', { waitFor: 'visible' }).then(() => done = true);
|
||||
for (let i = 0; i < 5; i++)
|
||||
await page.evaluate('1'); // Do a round trip.
|
||||
expect(done).toBe(false);
|
||||
await page.$eval('button', b => b.style.display = 'block');
|
||||
await clicked;
|
||||
expect(done).toBe(true);
|
||||
expect(await page.evaluate(() => result)).toBe('Clicked');
|
||||
});
|
||||
|
||||
it('should click wrapped links', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/wrappedlink.html');
|
||||
|
|
|
|||
|
|
@ -1207,19 +1207,16 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
|
|||
});
|
||||
it('should respect selector visibilty', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
await page.fill({selector: 'input', visibility: 'visible'}, 'some value');
|
||||
await page.fill('input', 'some value', { waitFor: 'visible' });
|
||||
expect(await page.evaluate(() => result)).toBe('some value');
|
||||
|
||||
let error = null;
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
await page.fill({selector: 'input', visibility: 'hidden'}, 'some value').catch(e => error = e);
|
||||
expect(error.message).toBe('No node found for selector: [hidden] input');
|
||||
|
||||
error = null;
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
await page.$eval('input', i => i.style.display = 'none');
|
||||
await page.fill({selector: 'input', visibility: 'visible'}, 'some value').catch(e => error = e);
|
||||
expect(error.message).toBe('No node found for selector: [visible] input');
|
||||
await Promise.all([
|
||||
page.fill('input', 'some value', { waitFor: 'visible' }),
|
||||
page.$eval('input', i => i.style.display = 'block'),
|
||||
]);
|
||||
expect(await page.evaluate(() => result)).toBe('some value');
|
||||
});
|
||||
it('should throw on disabled and readonly elements', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
|
|
|
|||
|
|
@ -61,19 +61,6 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
|||
const idAttribute = await page.$eval('section', e => e.id);
|
||||
expect(idAttribute).toBe('testAttribute');
|
||||
});
|
||||
it('should respect visibility', async({page, server}) => {
|
||||
let error = null;
|
||||
await page.setContent('<section id="testAttribute" style="display: none">43543</section>');
|
||||
await page.$eval({selector: 'css=section', visibility: 'visible'}, e => e.id).catch(e => error = e);
|
||||
expect(error.message).toContain('failed to find element matching selector "[visible] css=section"');
|
||||
expect(await page.$eval({selector: 'css=section', visibility: 'hidden'}, e => e.id)).toBe('testAttribute');
|
||||
|
||||
error = null;
|
||||
await page.setContent('<section id="testAttribute">43543</section>');
|
||||
await page.$eval({selector: 'css=section', visibility: 'hidden'}, e => e.id).catch(e => error = e);
|
||||
expect(error.message).toContain('failed to find element matching selector "[hidden] css=section"');
|
||||
expect(await page.$eval({selector: 'css=section', visibility: 'visible'}, e => e.id)).toBe('testAttribute');
|
||||
});
|
||||
it('should accept arguments', async({page, server}) => {
|
||||
await page.setContent('<section>hello</section>');
|
||||
const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!');
|
||||
|
|
@ -153,12 +140,6 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
|||
const spansCount = await page.$$eval('css=div >> css=div >> css=span', spans => spans.length);
|
||||
expect(spansCount).toBe(2);
|
||||
});
|
||||
it('should respect visibility', async({page, server}) => {
|
||||
await page.setContent('<section style="display: none">1</section><section style="display: none">2</section><section>3</section>');
|
||||
expect(await page.$$eval({selector: 'css=section', visibility: 'visible'}, x => x.length)).toBe(1);
|
||||
expect(await page.$$eval({selector: 'css=section', visibility: 'hidden'}, x => x.length)).toBe(2);
|
||||
expect(await page.$$eval({selector: 'css=section'}, x => x.length)).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page.$', function() {
|
||||
|
|
@ -181,6 +162,25 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
|||
const element = await page.$('non-existing-element');
|
||||
expect(element).toBe(null);
|
||||
});
|
||||
it('should return null for non-existing element with waitFor:false', async({page, server}) => {
|
||||
const element = await page.$('non-existing-element', { waitFor: false });
|
||||
expect(element).toBe(null);
|
||||
});
|
||||
it('should query existing element with waitFor:false', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
const element = await page.$('css=section', { waitFor: false });
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
it('should throw for unknown waitFor option', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
const error = await page.$('section', { waitFor: 'foo' }).catch(e => e);
|
||||
expect(error.message).toContain('Unsupported waitFor option');
|
||||
});
|
||||
it('should throw for numeric waitFor option', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
const error = await page.$('section', { waitFor: 123 }).catch(e => e);
|
||||
expect(error.message).toContain('Unsupported waitFor option');
|
||||
});
|
||||
it('should auto-detect xpath selector', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
const element = await page.$('//html/body/section');
|
||||
|
|
@ -201,16 +201,16 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
|||
const element = await page.$('css=section >> css=div');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
it('should respect visibility', async({page, server}) => {
|
||||
it('should respect waitFor visibility', async({page, server}) => {
|
||||
await page.setContent('<section id="testAttribute">43543</section>');
|
||||
expect(await page.$({selector: 'css=section', visibility: 'visible'})).toBeTruthy();
|
||||
expect(await page.$({selector: 'css=section', visibility: 'hidden'})).not.toBeTruthy();
|
||||
expect(await page.$({selector: 'css=section'})).toBeTruthy();
|
||||
expect(await page.$('css=section', { waitFor: 'visible'})).toBeTruthy();
|
||||
expect(await page.$('css=section', { waitFor: 'any'})).toBeTruthy();
|
||||
expect(await page.$('css=section')).toBeTruthy();
|
||||
|
||||
await page.setContent('<section id="testAttribute" style="display: none">43543</section>');
|
||||
expect(await page.$({selector: 'css=section', visibility: 'visible'})).not.toBeTruthy();
|
||||
expect(await page.$({selector: 'css=section', visibility: 'hidden'})).toBeTruthy();
|
||||
expect(await page.$({selector: 'css=section'})).toBeTruthy();
|
||||
expect(await page.$('css=section', { waitFor: 'hidden'})).toBeTruthy();
|
||||
expect(await page.$('css=section', { waitFor: 'any'})).toBeTruthy();
|
||||
expect(await page.$('css=section')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -227,12 +227,6 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
|||
const elements = await page.$$('div');
|
||||
expect(elements.length).toBe(0);
|
||||
});
|
||||
it('should respect visibility', async({page, server}) => {
|
||||
await page.setContent('<section style="display: none">1</section><section style="display: none">2</section><section>3</section>');
|
||||
expect((await page.$$({selector: 'css=section', visibility: 'visible'})).length).toBe(1);
|
||||
expect((await page.$$({selector: 'css=section', visibility: 'hidden'})).length).toBe(2);
|
||||
expect((await page.$$({selector: 'css=section'})).length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Path.$x', function() {
|
||||
|
|
@ -281,22 +275,6 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
|||
const second = await html.$('.third');
|
||||
expect(second).toBe(null);
|
||||
});
|
||||
|
||||
it('should respect visibility', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/playground.html');
|
||||
await page.setContent('<html><body><div class="second"><div class="inner" style="display:none">A</div></div></body></html>');
|
||||
const second = await page.$('html .second');
|
||||
|
||||
let inner = await second.$({selector: '.inner', visibility: 'visible'});
|
||||
expect(inner).not.toBeTruthy();
|
||||
|
||||
inner = await second.$({selector: '.inner', visibility: 'hidden'});
|
||||
expect(await inner.evaluate(e => e.textContent)).toBe('A');
|
||||
|
||||
await inner.evaluate(e => e.style.display = 'block');
|
||||
inner = await second.$({selector: '.inner', visibility: 'visible'});
|
||||
expect(await inner.evaluate(e => e.textContent)).toBe('A');
|
||||
});
|
||||
});
|
||||
describe('ElementHandle.$eval', function() {
|
||||
it('should work', async({page, server}) => {
|
||||
|
|
|
|||
|
|
@ -205,21 +205,21 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
});
|
||||
});
|
||||
|
||||
describe('Frame.waitForSelector', function() {
|
||||
describe('Frame.$ waitFor', function() {
|
||||
const addElement = tag => document.body.appendChild(document.createElement(tag));
|
||||
|
||||
it('should immediately resolve promise if node exists', async({page, server}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const frame = page.mainFrame();
|
||||
await frame.waitForSelector('*');
|
||||
await frame.$('*', { waitFor: true });
|
||||
await frame.evaluate(addElement, 'div');
|
||||
await frame.waitForSelector('div');
|
||||
await frame.$('div', { waitFor: true });
|
||||
});
|
||||
|
||||
it.skip(FFOX)('should work with removed MutationObserver', async({page, server}) => {
|
||||
await page.evaluate(() => delete window.MutationObserver);
|
||||
const [handle] = await Promise.all([
|
||||
page.waitForSelector('.zombo'),
|
||||
page.$('.zombo', { waitFor: true }),
|
||||
page.setContent(`<div class='zombo'>anything</div>`),
|
||||
]);
|
||||
expect(await page.evaluate(x => x.textContent, handle)).toBe('anything');
|
||||
|
|
@ -228,7 +228,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
it('should resolve promise when node is added', async({page, server}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const frame = page.mainFrame();
|
||||
const watchdog = frame.waitForSelector('div');
|
||||
const watchdog = frame.$('div', { waitFor: true });
|
||||
await frame.evaluate(addElement, 'br');
|
||||
await frame.evaluate(addElement, 'div');
|
||||
const eHandle = await watchdog;
|
||||
|
|
@ -238,17 +238,17 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
|
||||
it('should work when node is added through innerHTML', async({page, server}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const watchdog = page.waitForSelector('h3 div');
|
||||
const watchdog = page.$('h3 div', { waitFor: true });
|
||||
await page.evaluate(addElement, 'span');
|
||||
await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>');
|
||||
await watchdog;
|
||||
});
|
||||
|
||||
it('Page.waitForSelector is shortcut for main frame', async({page, server}) => {
|
||||
it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
||||
const otherFrame = page.frames()[1];
|
||||
const watchdog = page.waitForSelector('div');
|
||||
const watchdog = page.$('div', { waitFor: true });
|
||||
await otherFrame.evaluate(addElement, 'div');
|
||||
await page.evaluate(addElement, 'div');
|
||||
const eHandle = await watchdog;
|
||||
|
|
@ -260,7 +260,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
|
||||
const frame1 = page.frames()[1];
|
||||
const frame2 = page.frames()[2];
|
||||
const waitForSelectorPromise = frame2.waitForSelector('div');
|
||||
const waitForSelectorPromise = frame2.$('div', { waitFor: true });
|
||||
await frame1.evaluate(addElement, 'div');
|
||||
await frame2.evaluate(addElement, 'div');
|
||||
const eHandle = await waitForSelectorPromise;
|
||||
|
|
@ -271,7 +271,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
||||
const frame = page.frames()[1];
|
||||
let waitError = null;
|
||||
const waitPromise = frame.waitForSelector('.box').catch(e => waitError = e);
|
||||
const waitPromise = frame.$('.box', { waitFor: true }).catch(e => waitError = e);
|
||||
await utils.detachFrame(page, 'frame1');
|
||||
await waitPromise;
|
||||
expect(waitError).toBeTruthy();
|
||||
|
|
@ -279,7 +279,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
});
|
||||
it('should survive cross-process navigation', async({page, server}) => {
|
||||
let boxFound = false;
|
||||
const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true);
|
||||
const waitForSelector = page.$('.box', { waitFor: true }).then(() => boxFound = true);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
expect(boxFound).toBe(false);
|
||||
await page.reload();
|
||||
|
|
@ -290,7 +290,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
});
|
||||
it('should wait for visible', async({page, server}) => {
|
||||
let divFound = false;
|
||||
const waitForSelector = page.waitForSelector({selector: 'div', visibility: 'visible'}).then(() => divFound = true);
|
||||
const waitForSelector = page.$('div', { waitFor: 'visible' }).then(() => divFound = true);
|
||||
await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`);
|
||||
expect(divFound).toBe(false);
|
||||
await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
|
||||
|
|
@ -301,7 +301,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
});
|
||||
it('should wait for visible recursively', async({page, server}) => {
|
||||
let divVisible = false;
|
||||
const waitForSelector = page.waitForSelector({selector: 'div#inner', visibility: 'visible'}).then(() => divVisible = true);
|
||||
const waitForSelector = page.$('div#inner', { waitFor: 'visible' }).then(() => divVisible = true);
|
||||
await page.setContent(`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`);
|
||||
expect(divVisible).toBe(false);
|
||||
await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
|
||||
|
|
@ -313,8 +313,8 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
it('hidden should wait for visibility: hidden', async({page, server}) => {
|
||||
let divHidden = false;
|
||||
await page.setContent(`<div style='display: block;'></div>`);
|
||||
const waitForSelector = page.waitForSelector({selector: 'div', visibility: 'hidden'}).then(() => divHidden = true);
|
||||
await page.waitForSelector('div'); // do a round trip
|
||||
const waitForSelector = page.$('div', { waitFor: 'hidden' }).then(() => divHidden = true);
|
||||
await page.$('div', { waitFor: true }); // do a round trip
|
||||
expect(divHidden).toBe(false);
|
||||
await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
|
||||
expect(await waitForSelector).toBe(true);
|
||||
|
|
@ -323,8 +323,8 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
it('hidden should wait for display: none', async({page, server}) => {
|
||||
let divHidden = false;
|
||||
await page.setContent(`<div style='display: block;'></div>`);
|
||||
const waitForSelector = page.waitForSelector({selector: 'div', visibility: 'hidden'}).then(() => divHidden = true);
|
||||
await page.waitForSelector('div'); // do a round trip
|
||||
const waitForSelector = page.$('div', { waitFor: 'hidden' }).then(() => divHidden = true);
|
||||
await page.$('div', { waitFor: true }); // do a round trip
|
||||
expect(divHidden).toBe(false);
|
||||
await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
|
||||
expect(await waitForSelector).toBe(true);
|
||||
|
|
@ -333,20 +333,20 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
it('hidden should wait for removal', async({page, server}) => {
|
||||
await page.setContent(`<div></div>`);
|
||||
let divRemoved = false;
|
||||
const waitForSelector = page.waitForSelector({selector: 'div', visibility: 'hidden'}).then(() => divRemoved = true);
|
||||
await page.waitForSelector('div'); // do a round trip
|
||||
const waitForSelector = page.$('div', { waitFor: 'hidden' }).then(() => divRemoved = true);
|
||||
await page.$('div', { waitFor: true }); // do a round trip
|
||||
expect(divRemoved).toBe(false);
|
||||
await page.evaluate(() => document.querySelector('div').remove());
|
||||
expect(await waitForSelector).toBe(true);
|
||||
expect(divRemoved).toBe(true);
|
||||
});
|
||||
it('should return null if waiting to hide non-existing element', async({page, server}) => {
|
||||
const handle = await page.waitForSelector({selector: 'non-existing', visibility: 'hidden' });
|
||||
const handle = await page.$('non-existing', { waitFor: 'hidden' });
|
||||
expect(handle).toBe(null);
|
||||
});
|
||||
it('should respect timeout', async({page, server}) => {
|
||||
let error = null;
|
||||
await page.waitForSelector('div', {timeout: 10}).catch(e => error = e);
|
||||
await page.$('div', { waitFor: true, timeout: 10 }).catch(e => error = e);
|
||||
expect(error).toBeTruthy();
|
||||
expect(error.message).toContain('waiting for selector "div" failed: timeout');
|
||||
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
||||
|
|
@ -354,34 +354,34 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => {
|
||||
await page.setContent(`<div></div>`);
|
||||
let error = null;
|
||||
await page.waitForSelector({selector: 'div', visibility: 'hidden'}, {timeout: 10}).catch(e => error = e);
|
||||
await page.$('div', { waitFor: 'hidden', timeout: 10 }).catch(e => error = e);
|
||||
expect(error).toBeTruthy();
|
||||
expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
|
||||
});
|
||||
|
||||
it('should respond to node attribute mutation', async({page, server}) => {
|
||||
let divFound = false;
|
||||
const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true);
|
||||
const waitForSelector = page.$('.zombo', { waitFor: true }).then(() => divFound = true);
|
||||
await page.setContent(`<div class='notZombo'></div>`);
|
||||
expect(divFound).toBe(false);
|
||||
await page.evaluate(() => document.querySelector('div').className = 'zombo');
|
||||
expect(await waitForSelector).toBe(true);
|
||||
});
|
||||
it('should return the element handle', async({page, server}) => {
|
||||
const waitForSelector = page.waitForSelector('.zombo');
|
||||
const waitForSelector = page.$('.zombo', { waitFor: true });
|
||||
await page.setContent(`<div class='zombo'>anything</div>`);
|
||||
expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything');
|
||||
});
|
||||
it('should have correct stack trace for timeout', async({page, server}) => {
|
||||
let error;
|
||||
await page.waitForSelector('.zombo', {timeout: 10}).catch(e => error = e);
|
||||
await page.$('.zombo', { waitFor: true, timeout: 10 }).catch(e => error = e);
|
||||
expect(error.stack).toContain('waittask.spec.js');
|
||||
});
|
||||
|
||||
it('should support >> selector syntax', async({page, server}) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const frame = page.mainFrame();
|
||||
const watchdog = frame.waitForSelector('css=div >> css=span');
|
||||
const watchdog = frame.$('css=div >> css=span', { waitFor: true });
|
||||
await frame.evaluate(addElement, 'br');
|
||||
await frame.evaluate(addElement, 'div');
|
||||
await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span')));
|
||||
|
|
@ -391,19 +391,19 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
});
|
||||
});
|
||||
|
||||
describe('Frame.waitForXPath', function() {
|
||||
describe('Frame.$ waitFor xpath', function() {
|
||||
const addElement = tag => document.body.appendChild(document.createElement(tag));
|
||||
|
||||
it('should support some fancy xpath', async({page, server}) => {
|
||||
await page.setContent(`<p>red herring</p><p>hello world </p>`);
|
||||
const waitForXPath = page.waitForXPath('//p[normalize-space(.)="hello world"]');
|
||||
const waitForXPath = page.$('//p[normalize-space(.)="hello world"]', { waitFor: true });
|
||||
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world ');
|
||||
});
|
||||
it('should respect timeout', async({page}) => {
|
||||
let error = null;
|
||||
await page.waitForXPath('//div', {timeout: 10}).catch(e => error = e);
|
||||
await page.$('//div', { waitFor: true, timeout: 10 }).catch(e => error = e);
|
||||
expect(error).toBeTruthy();
|
||||
expect(error.message).toContain('waiting for selector "xpath=//div" failed: timeout');
|
||||
expect(error.message).toContain('waiting for selector "//div" failed: timeout');
|
||||
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
||||
});
|
||||
it('should run in specified frame', async({page, server}) => {
|
||||
|
|
@ -411,7 +411,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
|
||||
const frame1 = page.frames()[1];
|
||||
const frame2 = page.frames()[2];
|
||||
const waitForXPathPromise = frame2.waitForXPath('//div');
|
||||
const waitForXPathPromise = frame2.$('//div', { waitFor: true });
|
||||
await frame1.evaluate(addElement, 'div');
|
||||
await frame2.evaluate(addElement, 'div');
|
||||
const eHandle = await waitForXPathPromise;
|
||||
|
|
@ -421,20 +421,20 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
|||
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
||||
const frame = page.frames()[1];
|
||||
let waitError = null;
|
||||
const waitPromise = frame.waitForXPath('//*[@class="box"]').catch(e => waitError = e);
|
||||
const waitPromise = frame.$('//*[@class="box"]', { waitFor: true }).catch(e => waitError = e);
|
||||
await utils.detachFrame(page, 'frame1');
|
||||
await waitPromise;
|
||||
expect(waitError).toBeTruthy();
|
||||
expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
|
||||
});
|
||||
it('should return the element handle', async({page, server}) => {
|
||||
const waitForXPath = page.waitForXPath('//*[@class="zombo"]');
|
||||
const waitForXPath = page.$('//*[@class="zombo"]', { waitFor: true });
|
||||
await page.setContent(`<div class='zombo'>anything</div>`);
|
||||
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything');
|
||||
});
|
||||
it('should allow you to select an element with single slash', async({page, server}) => {
|
||||
await page.setContent(`<div>some text</div>`);
|
||||
const waitForXPath = page.waitForXPath('/html/body/div');
|
||||
const waitForXPath = page.$('//html/body/div', { waitFor: true });
|
||||
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue