fix(toBeHidden): return true to missing elements (#9205)
This commit is contained in:
parent
55ddc553a5
commit
f78302e8dd
|
|
@ -70,7 +70,7 @@ export type NavigationEvent = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchedulableTask<T> = (injectedScript: js.JSHandle<InjectedScript>) => Promise<js.JSHandle<InjectedScriptPoll<T>>>;
|
export type SchedulableTask<T> = (injectedScript: js.JSHandle<InjectedScript>) => Promise<js.JSHandle<InjectedScriptPoll<T>>>;
|
||||||
export type DomTaskBody<T, R> = (progress: InjectedScriptProgress, element: Element, data: T, elements: Element[], continuePolling: any) => R;
|
export type DomTaskBody<T, R, E> = (progress: InjectedScriptProgress, element: E, data: T, elements: Element[], continuePolling: any) => R;
|
||||||
|
|
||||||
export class FrameManager {
|
export class FrameManager {
|
||||||
private _page: Page;
|
private _page: Page;
|
||||||
|
|
@ -1165,9 +1165,18 @@ export class Frame extends SdkObject {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
const querySelectorAll = options.expression === 'to.have.count' || options.expression.endsWith('.array');
|
const querySelectorAll = options.expression === 'to.have.count' || options.expression.endsWith('.array');
|
||||||
const mainWorld = options.expression === 'to.have.property';
|
const mainWorld = options.expression === 'to.have.property';
|
||||||
|
const omitAttached = (!options.isNot && options.expression === 'to.be.hidden') || (options.isNot && options.expression === 'to.be.visible');
|
||||||
|
|
||||||
return await this._scheduleRerunnableTaskWithController(controller, selector, (progress, element, options, elements, continuePolling) => {
|
return await this._scheduleRerunnableTaskWithController(controller, selector, (progress, element, options, elements, continuePolling) => {
|
||||||
return progress.injectedScript.expect(progress, element, options, elements, continuePolling);
|
// We don't have an element and we don't need an element => pass.
|
||||||
}, options, { strict: true, querySelectorAll, mainWorld, logScale: true, ...options }).catch(e => {
|
if (!element && options.omitAttached)
|
||||||
|
return { pass: !options.isNot };
|
||||||
|
// We don't have an element and we DO need an element => fail.
|
||||||
|
if (!element)
|
||||||
|
return { pass: !!options.isNot };
|
||||||
|
// We have an element.
|
||||||
|
return progress.injectedScript.expect(progress, element!, options, elements, continuePolling);
|
||||||
|
}, { omitAttached, ...options }, { strict: true, querySelectorAll, mainWorld, omitAttached, logScale: true, ...options }).catch(e => {
|
||||||
if (js.isJavaScriptErrorInEvaluate(e))
|
if (js.isJavaScriptErrorInEvaluate(e))
|
||||||
throw e;
|
throw e;
|
||||||
return { received: controller.lastIntermediateResult(), pass: !!options.isNot, log: metadata.log };
|
return { received: controller.lastIntermediateResult(), pass: !!options.isNot, log: metadata.log };
|
||||||
|
|
@ -1231,17 +1240,17 @@ export class Frame extends SdkObject {
|
||||||
this._parentFrame = null;
|
this._parentFrame = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _scheduleRerunnableTask<T, R>(metadata: CallMetadata, selector: string, body: DomTaskBody<T, R>, taskData: T, options: types.TimeoutOptions & types.StrictOptions & { mainWorld?: boolean } = {}): Promise<R> {
|
private async _scheduleRerunnableTask<T, R>(metadata: CallMetadata, selector: string, body: DomTaskBody<T, R, Element>, taskData: T, options: types.TimeoutOptions & types.StrictOptions & { mainWorld?: boolean } = {}): Promise<R> {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
return this._scheduleRerunnableTaskWithController(controller, selector, body, taskData, options);
|
return this._scheduleRerunnableTaskWithController(controller, selector, body as DomTaskBody<T, R, Element | undefined>, taskData, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _scheduleRerunnableTaskWithController<T, R>(
|
private async _scheduleRerunnableTaskWithController<T, R>(
|
||||||
controller: ProgressController,
|
controller: ProgressController,
|
||||||
selector: string,
|
selector: string,
|
||||||
body: DomTaskBody<T, R>,
|
body: DomTaskBody<T, R, Element | undefined>,
|
||||||
taskData: T,
|
taskData: T,
|
||||||
options: types.TimeoutOptions & types.StrictOptions & { mainWorld?: boolean, querySelectorAll?: boolean, logScale?: boolean } = {}): Promise<R> {
|
options: types.TimeoutOptions & types.StrictOptions & { mainWorld?: boolean, querySelectorAll?: boolean, logScale?: boolean, omitAttached?: boolean } = {}): Promise<R> {
|
||||||
|
|
||||||
const info = this._page.parseSelector(selector, options);
|
const info = this._page.parseSelector(selector, options);
|
||||||
const callbackText = body.toString();
|
const callbackText = body.toString();
|
||||||
|
|
@ -1250,8 +1259,8 @@ export class Frame extends SdkObject {
|
||||||
return controller.run(async progress => {
|
return controller.run(async progress => {
|
||||||
progress.log(`waiting for selector "${selector}"`);
|
progress.log(`waiting for selector "${selector}"`);
|
||||||
const rerunnableTask = new RerunnableTask(data, progress, injectedScript => {
|
const rerunnableTask = new RerunnableTask(data, progress, injectedScript => {
|
||||||
return injectedScript.evaluateHandle((injected, { info, taskData, callbackText, querySelectorAll, logScale }) => {
|
return injectedScript.evaluateHandle((injected, { info, taskData, callbackText, querySelectorAll, logScale, omitAttached }) => {
|
||||||
const callback = injected.eval(callbackText) as DomTaskBody<T, R>;
|
const callback = injected.eval(callbackText) as DomTaskBody<T, R, Element | undefined>;
|
||||||
const poller = logScale ? injected.pollLogScale.bind(injected) : injected.pollRaf.bind(injected);
|
const poller = logScale ? injected.pollLogScale.bind(injected) : injected.pollRaf.bind(injected);
|
||||||
return poller((progress, continuePolling) => {
|
return poller((progress, continuePolling) => {
|
||||||
if (querySelectorAll) {
|
if (querySelectorAll) {
|
||||||
|
|
@ -1261,12 +1270,13 @@ export class Frame extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = injected.querySelector(info.parsed, document, info.strict);
|
const element = injected.querySelector(info.parsed, document, info.strict);
|
||||||
if (!element)
|
if (!element && !omitAttached)
|
||||||
return continuePolling;
|
return continuePolling;
|
||||||
progress.logRepeating(` selector resolved to ${injected.previewNode(element)}`);
|
if (element)
|
||||||
|
progress.logRepeating(` selector resolved to ${injected.previewNode(element)}`);
|
||||||
return callback(progress, element, taskData as T, [], continuePolling);
|
return callback(progress, element, taskData as T, [], continuePolling);
|
||||||
});
|
});
|
||||||
}, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale });
|
}, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale, omitAttached: options.omitAttached });
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
if (this._detached)
|
if (this._detached)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
printReceivedStringContainExpectedResult,
|
printReceivedStringContainExpectedResult,
|
||||||
printReceivedStringContainExpectedSubstring
|
printReceivedStringContainExpectedSubstring
|
||||||
} from 'expect/build/print';
|
} from 'expect/build/print';
|
||||||
|
import colors from 'colors/safe';
|
||||||
import { ExpectedTextValue } from '../../protocol/channels';
|
import { ExpectedTextValue } from '../../protocol/channels';
|
||||||
import { isRegExp, isString } from '../../utils/utils';
|
import { isRegExp, isString } from '../../utils/utils';
|
||||||
import { currentTestInfo } from '../globals';
|
import { currentTestInfo } from '../globals';
|
||||||
|
|
@ -122,6 +123,6 @@ export function callLogText(log: string[] | undefined): string {
|
||||||
return `
|
return `
|
||||||
|
|
||||||
Call log:
|
Call log:
|
||||||
${(log || []).join('\n')}
|
- ${colors.dim((log || []).join('\n - '))}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,12 @@ test('should support toBeVisible, toBeHidden', async ({ runInlineTest }) => {
|
||||||
await expect(locator).toBeHidden();
|
await expect(locator).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('was hidden', async ({ page }) => {
|
||||||
|
await page.setContent('<div</div>');
|
||||||
|
const locator = page.locator('button');
|
||||||
|
await expect(locator).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
test('not hidden', async ({ page }) => {
|
test('not hidden', async ({ page }) => {
|
||||||
await page.setContent('<input></input>');
|
await page.setContent('<input></input>');
|
||||||
const locator = page.locator('input');
|
const locator = page.locator('input');
|
||||||
|
|
@ -144,10 +150,96 @@ test('should support toBeVisible, toBeHidden', async ({ runInlineTest }) => {
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { workers: 1 });
|
}, { workers: 1 });
|
||||||
|
expect(result.passed).toBe(5);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support toBeVisible, toBeHidden wait', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('visible', async ({ page }) => {
|
||||||
|
await page.setContent('<div></div>');
|
||||||
|
const locator = page.locator('span');
|
||||||
|
setTimeout(() => {
|
||||||
|
page.$eval('div', div => div.innerHTML = '<span>Hello</span>').catch(() => {});
|
||||||
|
}, 0);
|
||||||
|
await expect(locator).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not hidden', async ({ page }) => {
|
||||||
|
await page.setContent('<div></div>');
|
||||||
|
const locator = page.locator('span');
|
||||||
|
setTimeout(() => {
|
||||||
|
page.$eval('div', div => div.innerHTML = '<span>Hello</span>').catch(() => {});
|
||||||
|
}, 0);
|
||||||
|
await expect(locator).not.toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not visible', async ({ page }) => {
|
||||||
|
await page.setContent('<div><span>Hello</span></div>');
|
||||||
|
const locator = page.locator('span');
|
||||||
|
setTimeout(() => {
|
||||||
|
page.$eval('span', span => span.textContent = '').catch(() => {});
|
||||||
|
}, 0);
|
||||||
|
await expect(locator).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hidden', async ({ page }) => {
|
||||||
|
await page.setContent('<div><span>Hello</span></div>');
|
||||||
|
const locator = page.locator('span');
|
||||||
|
setTimeout(() => {
|
||||||
|
page.$eval('span', span => span.textContent = '').catch(() => {});
|
||||||
|
}, 0);
|
||||||
|
await expect(locator).toBeHidden();
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
expect(result.passed).toBe(4);
|
expect(result.passed).toBe(4);
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should support toBeVisible, toBeHidden fail', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('visible', async ({ page }) => {
|
||||||
|
await page.setContent('<button style="display: none"></button>');
|
||||||
|
const locator = page.locator('button');
|
||||||
|
await expect(locator).toBeVisible({ timeout: 500 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not visible', async ({ page }) => {
|
||||||
|
await page.setContent('<input></input>');
|
||||||
|
const locator = page.locator('input');
|
||||||
|
await expect(locator).not.toBeVisible({ timeout: 500 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hidden', async ({ page }) => {
|
||||||
|
await page.setContent('<input></input>');
|
||||||
|
const locator = page.locator('input');
|
||||||
|
await expect(locator).toBeHidden({ timeout: 500 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not hidden', async ({ page }) => {
|
||||||
|
await page.setContent('<button style="display: none"></button>');
|
||||||
|
const locator = page.locator('button');
|
||||||
|
await expect(locator).not.toBeHidden({ timeout: 500 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not hidden 2', async ({ page }) => {
|
||||||
|
await page.setContent('<div></div>');
|
||||||
|
const locator = page.locator('button');
|
||||||
|
await expect(locator).not.toBeHidden({ timeout: 500 });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.failed).toBe(5);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should support toBeFocused', async ({ runInlineTest }) => {
|
test('should support toBeFocused', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue