playwright/tests/page/page-network-idle.spec.ts
Dmitry Gozman 4bec6309df
fix(lifecycle): recalculate lifecycle on iframe detach (#15812)
It could be that iframe was blocking some event, like load or networkidle,
and we never updated the lifecycle after the iframe was detached. This
lead to goto and other navigation commands to never resolve.
2022-07-22 19:44:02 -07:00

175 lines
6.7 KiB
TypeScript

/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { test as it, expect } from './pageTest';
import type { Frame } from 'playwright-core';
import type { TestServer } from '../../utils/testserver';
it('should navigate to empty page with networkidle', async ({ page, server }) => {
const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' });
expect(response.status()).toBe(200);
});
async function networkIdleTest(frame: Frame, server: TestServer, action: () => Promise<any>, isSetContent?: boolean) {
const waitForRequest = (suffix: string) => {
return Promise.all([
server.waitForRequest(suffix),
frame.page().waitForRequest(server.PREFIX + suffix),
]);
};
let responseA, responseB;
// Hold on to a bunch of requests without answering.
server.setRoute('/fetch-request-a.js', (req, res) => responseA = res);
const firstFetchResourceRequested = waitForRequest('/fetch-request-a.js');
server.setRoute('/fetch-request-b.js', (req, res) => responseB = res);
const secondFetchResourceRequested = waitForRequest('/fetch-request-b.js');
const waitForLoadPromise = isSetContent ? Promise.resolve() : frame.waitForNavigation({ waitUntil: 'load' });
// Navigate to a page which loads immediately and then does a bunch of
// requests via javascript's fetch method.
const actionPromise = action();
// Track when the action gets completed.
let actionFinished = false;
actionPromise.then(() => actionFinished = true);
// Wait for the frame's 'load' event.
await waitForLoadPromise;
expect(actionFinished).toBe(false);
// Wait for the initial resource to be requested.
await firstFetchResourceRequested;
expect(actionFinished).toBe(false);
// Trigger the second request.
await frame.page().evaluate(() => window['fetchSecond']());
// Finish the first request.
responseA.statusCode = 404;
responseA.end(`File not found`);
// Wait for the second round to be requested.
await secondFetchResourceRequested;
expect(actionFinished).toBe(false);
// Finishing the second response should trigger networkidle.
let timerTriggered = false;
const timer = setTimeout(() => timerTriggered = true, 500);
responseB.statusCode = 404;
responseB.end(`File not found`);
const response = await actionPromise;
clearTimeout(timer);
expect(timerTriggered).toBe(true);
if (!isSetContent)
expect(response.ok()).toBe(true);
}
it('should wait for networkidle to succeed navigation', async ({ page, server }) => {
await networkIdleTest(page.mainFrame(), server, () => {
return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' });
});
});
it('should wait for networkidle to succeed navigation with request from previous navigation', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
server.setRoute('/foo.js', () => {});
await page.setContent(`<script>fetch('foo.js');</script>`);
await networkIdleTest(page.mainFrame(), server, () => {
return page.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' });
});
});
it('should wait for networkidle in waitForNavigation', async ({ page, server }) => {
await networkIdleTest(page.mainFrame(), server, () => {
const promise = page.waitForNavigation({ waitUntil: 'networkidle' });
page.goto(server.PREFIX + '/networkidle.html');
return promise;
});
});
it('should wait for networkidle in setContent', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await networkIdleTest(page.mainFrame(), server, () => {
return page.setContent(`<script src='networkidle.js'></script>`, { waitUntil: 'networkidle' });
}, true);
});
it('should wait for networkidle in setContent with request from previous navigation', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
server.setRoute('/foo.js', () => {});
await page.setContent(`<script>fetch('foo.js');</script>`);
await networkIdleTest(page.mainFrame(), server, () => {
return page.setContent(`<script src='networkidle.js'></script>`, { waitUntil: 'networkidle' });
}, true);
});
it('should wait for networkidle when navigating iframe', async ({ page, server }) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
const frame = page.mainFrame().childFrames()[0];
await networkIdleTest(frame, server, () => frame.goto(server.PREFIX + '/networkidle.html', { waitUntil: 'networkidle' }));
});
it('should wait for networkidle in setContent from the child frame', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await networkIdleTest(page.mainFrame(), server, () => {
return page.setContent(`<iframe src='networkidle.html'></iframe>`, { waitUntil: 'networkidle' });
}, true);
});
it('should wait for networkidle from the child frame', async ({ page, server }) => {
await networkIdleTest(page.mainFrame(), server, () => {
return page.goto(server.PREFIX + '/networkidle-frame.html', { waitUntil: 'networkidle' });
});
});
it('should wait for networkidle from the popup', async ({ page, server, isAndroid }) => {
it.skip(isAndroid, 'Too slow');
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<button id=box1 onclick="window.open('./popup/popup.html')">Button1</button>
<button id=box2 onclick="window.open('./popup/popup.html')">Button2</button>
<button id=box3 onclick="window.open('./popup/popup.html')">Button3</button>
<button id=box4 onclick="window.open('./popup/popup.html')">Button4</button>
<button id=box5 onclick="window.open('./popup/popup.html')">Button5</button>
`);
for (let i = 1; i < 6; ++i) {
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('#box' + i)
]);
await popup.waitForLoadState('networkidle');
}
});
it('should wait for networkidle when iframe attaches and detaches', async ({ page }) => {
await page.setContent(`
<body>
<script>
setTimeout(() => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
setTimeout(() => iframe.remove(), 400);
}, 400);
</script>
</body>
`, { waitUntil: 'networkidle' });
expect(await page.$('iframe')).toBe(null);
});