mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
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;
|
||||
return state === 'error:notconnected' ? false : state;
|
||||
}, { 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> {
|
||||
|
@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { rewriteErrorMessage } from './stackTrace';
|
||||
|
||||
export class ManualPromise<T = void> extends Promise<T> {
|
||||
private _resolve!: (t: T) => void;
|
||||
private _reject!: (e: Error) => void;
|
||||
@ -56,12 +58,14 @@ export class ManualPromise<T = void> extends Promise<T> {
|
||||
|
||||
export class ScopedRace {
|
||||
private _terminateError: Error | undefined;
|
||||
private _terminatePromises = new Set<ManualPromise<Error>>();
|
||||
private _terminatePromises = new Map<ManualPromise<Error>, Error>();
|
||||
|
||||
scopeClosed(error: Error) {
|
||||
this._terminateError = error;
|
||||
for (const p of this._terminatePromises)
|
||||
p.resolve(error);
|
||||
for (const [p, e] of this._terminatePromises) {
|
||||
rewriteErrorMessage(e, error.message);
|
||||
p.resolve(e);
|
||||
}
|
||||
}
|
||||
|
||||
async race<T>(promise: Promise<T>): Promise<T> {
|
||||
@ -76,7 +80,8 @@ export class ScopedRace {
|
||||
const terminatePromise = new ManualPromise<Error>();
|
||||
if (this._terminateError)
|
||||
terminatePromise.resolve(this._terminateError);
|
||||
this._terminatePromises.add(terminatePromise);
|
||||
const error = new Error('');
|
||||
this._terminatePromises.set(terminatePromise, error);
|
||||
try {
|
||||
return await Promise.race([
|
||||
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 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 New Issue
Block a user