fix(toBeHidden): return true to missing elements (#9205)

This commit is contained in:
Pavel Feldman 2021-09-28 17:11:04 -07:00 committed by GitHub
parent 55ddc553a5
commit f78302e8dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 116 additions and 13 deletions

View file

@ -70,7 +70,7 @@ export type NavigationEvent = {
};
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 {
private _page: Page;
@ -1165,9 +1165,18 @@ export class Frame extends SdkObject {
const controller = new ProgressController(metadata, this);
const querySelectorAll = options.expression === 'to.have.count' || options.expression.endsWith('.array');
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 progress.injectedScript.expect(progress, element, options, elements, continuePolling);
}, options, { strict: true, querySelectorAll, mainWorld, logScale: true, ...options }).catch(e => {
// We don't have an element and we don't need an element => pass.
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))
throw e;
return { received: controller.lastIntermediateResult(), pass: !!options.isNot, log: metadata.log };
@ -1231,17 +1240,17 @@ export class Frame extends SdkObject {
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);
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>(
controller: ProgressController,
selector: string,
body: DomTaskBody<T, R>,
body: DomTaskBody<T, R, Element | undefined>,
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 callbackText = body.toString();
@ -1250,8 +1259,8 @@ export class Frame extends SdkObject {
return controller.run(async progress => {
progress.log(`waiting for selector "${selector}"`);
const rerunnableTask = new RerunnableTask(data, progress, injectedScript => {
return injectedScript.evaluateHandle((injected, { info, taskData, callbackText, querySelectorAll, logScale }) => {
const callback = injected.eval(callbackText) as DomTaskBody<T, R>;
return injectedScript.evaluateHandle((injected, { info, taskData, callbackText, querySelectorAll, logScale, omitAttached }) => {
const callback = injected.eval(callbackText) as DomTaskBody<T, R, Element | undefined>;
const poller = logScale ? injected.pollLogScale.bind(injected) : injected.pollRaf.bind(injected);
return poller((progress, continuePolling) => {
if (querySelectorAll) {
@ -1261,12 +1270,13 @@ export class Frame extends SdkObject {
}
const element = injected.querySelector(info.parsed, document, info.strict);
if (!element)
if (!element && !omitAttached)
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);
});
}, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale });
}, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale, omitAttached: options.omitAttached });
}, true);
if (this._detached)

View file

@ -18,6 +18,7 @@ import {
printReceivedStringContainExpectedResult,
printReceivedStringContainExpectedSubstring
} from 'expect/build/print';
import colors from 'colors/safe';
import { ExpectedTextValue } from '../../protocol/channels';
import { isRegExp, isString } from '../../utils/utils';
import { currentTestInfo } from '../globals';
@ -122,6 +123,6 @@ export function callLogText(log: string[] | undefined): string {
return `
Call log:
${(log || []).join('\n')}
- ${colors.dim((log || []).join('\n - '))}
`;
}

View file

@ -137,6 +137,12 @@ test('should support toBeVisible, toBeHidden', async ({ runInlineTest }) => {
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 }) => {
await page.setContent('<input></input>');
const locator = page.locator('input');
@ -144,10 +150,96 @@ test('should support toBeVisible, toBeHidden', async ({ runInlineTest }) => {
});
`,
}, { 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.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 }) => {
const result = await runInlineTest({
'a.test.ts': `