fix(isVisible): return false during navigation (#22943)
Instead of throwing "Execution context was destroyed" error. Drive-by: improve internal error messages for `ScopedRace` errors. Fixes #22925.
This commit is contained in:
parent
c9dad439cd
commit
f2ad5bbfbb
|
|
@ -1288,7 +1288,11 @@ export class Frame extends SdkObject {
|
||||||
const state = element ? injected.elementState(element, 'visible') : false;
|
const state = element ? injected.elementState(element, 'visible') : false;
|
||||||
return state === 'error:notconnected' ? false : state;
|
return state === 'error:notconnected' ? false : state;
|
||||||
}, { info: resolved.info });
|
}, { info: resolved.info });
|
||||||
}, this._page._timeoutSettings.timeout({}));
|
}, this._page._timeoutSettings.timeout({})).catch(e => {
|
||||||
|
if (js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e) || isSessionClosedError(e))
|
||||||
|
throw e;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async isHidden(metadata: CallMetadata, selector: string, options: types.StrictOptions = {}): Promise<boolean> {
|
async isHidden(metadata: CallMetadata, selector: string, options: types.StrictOptions = {}): Promise<boolean> {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { rewriteErrorMessage } from './stackTrace';
|
||||||
|
|
||||||
export class ManualPromise<T = void> extends Promise<T> {
|
export class ManualPromise<T = void> extends Promise<T> {
|
||||||
private _resolve!: (t: T) => void;
|
private _resolve!: (t: T) => void;
|
||||||
private _reject!: (e: Error) => void;
|
private _reject!: (e: Error) => void;
|
||||||
|
|
@ -56,12 +58,14 @@ export class ManualPromise<T = void> extends Promise<T> {
|
||||||
|
|
||||||
export class ScopedRace {
|
export class ScopedRace {
|
||||||
private _terminateError: Error | undefined;
|
private _terminateError: Error | undefined;
|
||||||
private _terminatePromises = new Set<ManualPromise<Error>>();
|
private _terminatePromises = new Map<ManualPromise<Error>, Error>();
|
||||||
|
|
||||||
scopeClosed(error: Error) {
|
scopeClosed(error: Error) {
|
||||||
this._terminateError = error;
|
this._terminateError = error;
|
||||||
for (const p of this._terminatePromises)
|
for (const [p, e] of this._terminatePromises) {
|
||||||
p.resolve(error);
|
rewriteErrorMessage(e, error.message);
|
||||||
|
p.resolve(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async race<T>(promise: Promise<T>): Promise<T> {
|
async race<T>(promise: Promise<T>): Promise<T> {
|
||||||
|
|
@ -76,7 +80,8 @@ export class ScopedRace {
|
||||||
const terminatePromise = new ManualPromise<Error>();
|
const terminatePromise = new ManualPromise<Error>();
|
||||||
if (this._terminateError)
|
if (this._terminateError)
|
||||||
terminatePromise.resolve(this._terminateError);
|
terminatePromise.resolve(this._terminateError);
|
||||||
this._terminatePromises.add(terminatePromise);
|
const error = new Error('');
|
||||||
|
this._terminatePromises.set(terminatePromise, error);
|
||||||
try {
|
try {
|
||||||
return await Promise.race([
|
return await Promise.race([
|
||||||
terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)),
|
terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)),
|
||||||
|
|
|
||||||
|
|
@ -85,3 +85,23 @@ it('isVisible inside a role=button', async ({ page }) => {
|
||||||
await span.waitFor({ state: 'hidden' });
|
await span.waitFor({ state: 'hidden' });
|
||||||
await page.locator('[role=button]').waitFor({ state: 'visible' });
|
await page.locator('[role=button]').waitFor({ state: 'visible' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('isVisible during navigation should not throw', async ({ page, server }) => {
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
// Make sure previous navigation finishes, to avoid page.setContent throwing.
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
await page.setContent(`
|
||||||
|
<script>
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = ${JSON.stringify(server.EMPTY_PAGE)};
|
||||||
|
}, Math.random(50));
|
||||||
|
</script>
|
||||||
|
`);
|
||||||
|
expect(await page.locator('div').isVisible()).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isVisible with invalid selector should throw', async ({ page }) => {
|
||||||
|
const error = await page.locator('hey=what').isVisible().catch(e => e);
|
||||||
|
expect(error.message).toContain('Unknown engine "hey" while parsing selector hey=what');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue