/**
* @license
* Copyright 2018 Google Inc.
* SPDX-License-Identifier: Apache-2.0
*/
import fs from
'node:fs';
import type {ServerResponse} from
'node:http';
import path from
'node:path';
import expect from
'expect';
import type {HTTPRequest} from
'puppeteer-core/internal/api/HTTPRequest.js';
import type {HTTPResponse} from
'puppeteer-core/internal/api/HTTPResponse.js';
import {getTestState, launch, setupTestBrowserHooks} from
'./mocha-utils.js';
import {attachFrame, isFavicon, waitEvent} from
'./utils.js';
describe(
'network',
function () {
setupTestBrowserHooks();
describe(
'Page.Events.Request',
function () {
it(
'should fire for navigation requests', async () => {
const {page, server} = await getTestState();
const requests: HTTPRequest[] = [];
page.on(
'request', request => {
return !isFavicon(request) && requests.push(request);
});
await page.
goto(server.EMPTY_PAGE);
expect(requests).toHaveLength(
1);
});
it(
'should fire for iframes', async () => {
const {page, server} = await getTestState();
const requests: HTTPRequest[] = [];
page.on(
'request', request => {
return !isFavicon(request) && requests.push(request);
});
await page.
goto(server.EMPTY_PAGE);
await attachFrame(page,
'frame1', server.EMPTY_PAGE);
expect(requests).toHaveLength(
2);
});
it(
'should fire for fetches', async () => {
const {page, server} = await getTestState();
const requests: HTTPRequest[] = [];
page.on(
'request', request => {
return !isFavicon(request) && requests.push(request);
});
await page.
goto(server.EMPTY_PAGE);
await page.evaluate(() => {
return fetch(
'/empty.html');
});
expect(requests).toHaveLength(
2);
});
});
describe(
'Request.frame',
function () {
it(
'should work for main frame navigation request', async () => {
const {page, server} = await getTestState();
const requests: HTTPRequest[] = [];
page.on(
'request', request => {
return !isFavicon(request) && requests.push(request);
});
await page.
goto(server.EMPTY_PAGE);
expect(requests).toHaveLength(
1);
expect(requests[
0]!.frame()).toBe(page.mainFrame());
});
it(
'should work for subframe navigation request', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
const requests: HTTPRequest[] = [];
page.on(
'request', request => {
return !isFavicon(request) && requests.push(request);
});
await attachFrame(page,
'frame1', server.EMPTY_PAGE);
expect(requests).toHaveLength(
1);
expect(requests[
0]!.frame()).toBe(page.frames()[
1]);
});
it(
'should work for fetch requests', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
let requests: HTTPRequest[] = [];
page.on(
'request', request => {
return !isFavicon(request) && requests.push(request);
});
await page.evaluate(() => {
return fetch(
'/digits/1.png');
});
requests = requests.filter(request => {
return !request.url().includes(
'favicon');
});
expect(requests).toHaveLength(
1);
expect(requests[
0]!.frame()).toBe(page.mainFrame());
});
});
describe(
'Request.headers',
function () {
it(
'should define Browser in user agent header', async () => {
const {page, server, isChrome} = await getTestState();
const response = (await page.
goto(server.EMPTY_PAGE))!;
const userAgent = response.request().headers()[
'user-agent'];
if (isChrome) {
expect(userAgent).toContain(
'Chrome');
}
else {
expect(userAgent).toContain(
'Firefox');
}
});
// In CDP cookie header is only when the request actually
// hits the network, verify that we populate the correctly
describe(
'cookie header', () => {
it(
'should show Cookie header', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie =
'username=John Doe';
});
const response = (await page.
goto(server.PREFIX +
'/title.html'))!;
const cookie = response.request().headers()[
'cookie'];
expect(cookie).toContain(
'username=John Doe');
});
it(
'should show Cookie header for redirect', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
server.setRedirect(
'/foo.html',
'/title.html');
await page.evaluate(() => {
document.cookie =
'username=John Doe';
});
const response = (await page.
goto(server.PREFIX +
'/foo.html'))!;
const cookie1 = response.request().redirectChain()[
0]!.headers()[
'cookie'
];
expect(cookie1).toContain(
'username=John Doe');
const cookie2 = response.request().headers()[
'cookie'];
expect(cookie2).toContain(
'username=John Doe');
});
it(
'should show Cookie header for fetch request', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
await page.evaluate(() => {
document.cookie =
'username=John Doe';
});
const [response] = await Promise.all([
waitEvent<HTTPResponse>(page,
'response', req => {
return !isFavicon(req);
}),
page.evaluate(async () => {
await fetch(
'/title.html');
}),
]);
const cookie = response.request().headers()[
'cookie'];
expect(cookie).toContain(
'username=John Doe');
});
});
});
describe(
'Response.headers',
function () {
it(
'should work', async () => {
const {page, server} = await getTestState();
server.setRoute(
'/empty.html', (_req, res) => {
res.setHeader(
'foo',
'bar');
res.end();
});
const response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.headers()[
'foo']).toBe(
'bar');
});
});
describe(
'Request.initiator', () => {
it(
'should return the initiator', async () => {
const {page, server} = await getTestState();
const initiators =
new Map();
page.on(
'request', request => {
return initiators.set(
request.url().split(
'/').pop(),
request.initiator(),
);
});
await page.
goto(server.PREFIX +
'/initiator.html');
expect(initiators.get(
'initiator.html').type).toBe(
'other');
expect(initiators.get(
'initiator.js').type).toBe(
'parser');
expect(initiators.get(
'initiator.js').url).toBe(
server.PREFIX +
'/initiator.html',
);
expect(initiators.get(
'frame.html').type).toBe(
'parser');
expect(initiators.get(
'frame.html').url).toBe(
server.PREFIX +
'/initiator.html',
);
expect(initiators.get(
'script.js').type).toBe(
'parser');
expect(initiators.get(
'script.js').url).toBe(
server.PREFIX +
'/frames/frame.html',
);
expect(initiators.get(
'style.css').type).toBe(
'parser');
expect(initiators.get(
'style.css').url).toBe(
server.PREFIX +
'/frames/frame.html',
);
expect(initiators.get(
'initiator.js').type).toBe(
'parser');
expect(initiators.get(
'injectedfile.js').type).toBe(
'script');
expect(initiators.get(
'injectedfile.js').stack.callFrames[
0]!.url).toBe(
server.PREFIX +
'/initiator.js',
);
expect(initiators.get(
'injectedstyle.css').type).toBe(
'script');
expect(initiators.get(
'injectedstyle.css').stack.callFrames[
0]!.url).toBe(
server.PREFIX +
'/initiator.js',
);
expect(initiators.get(
'initiator.js').url).toBe(
server.PREFIX +
'/initiator.html',
);
});
});
describe(
'Response.fromCache',
function () {
it(
'should return |false| for non-cached content', async () => {
const {page, server} = await getTestState();
const response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.fromCache()).toBe(
false);
});
// Run this cache test both with a stylesheet and a script.
// Firefox currently does not handle network events for cached CSS files.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1879438
for (
const {html, resource, type} of [
{html:
'one-style.html', resource:
'one-style.css', type:
'stylesheet'},
{html:
'one-script.html', resource:
'one-script.js', type:
'script'},
]) {
it(`should work
for ${type}`, async () => {
const {page, server} = await getTestState();
const responses =
new Map();
page.on(
'response', r => {
return (
!isFavicon(r.request()) &&
responses.set(r.url().split(
'/').pop(), r)
);
});
// Load and re-load to make sure it's cached.
await page.
goto(server.PREFIX +
'/cached/' + html);
await page.reload();
expect(responses.size).toBe(
2);
expect(responses.get(resource).status()).toBe(
200);
expect(responses.get(resource).fromCache()).toBe(
true);
expect(responses.get(html).status()).toBe(
304);
expect(responses.get(html).fromCache()).toBe(
false);
});
}
});
describe(
'Response.fromServiceWorker',
function () {
it(
'should return |false| for non-service-worker content', async () => {
const {page, server} = await getTestState();
const response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.fromServiceWorker()).toBe(
false);
});
it(
'Response.fromServiceWorker', async () => {
const {page, server} = await getTestState();
const responses =
new Map();
page.on(
'response', r => {
return !isFavicon(r) && responses.set(r.url().split(
'/').pop(), r);
});
// Load and re-load to make sure serviceworker is installed and running.
await page.
goto(server.PREFIX +
'/serviceworkers/fetch/sw.html', {
waitUntil:
'networkidle2',
});
await page.evaluate(async () => {
return (globalThis as any).activationPromise;
});
await page.reload();
expect(responses.size).toBe(
2);
expect(responses.get(
'sw.html').status()).toBe(
200);
expect(responses.get(
'sw.html').fromServiceWorker()).toBe(
true);
expect(responses.get(
'style.css').status()).toBe(
200);
expect(responses.get(
'style.css').fromServiceWorker()).toBe(
true);
});
});
describe(
'Request.fetchPostData',
function () {
it(
'should work', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
server.setRoute(
'/post', (_req, res) => {
return res.end();
});
const [request] = await Promise.all([
waitEvent<HTTPRequest>(page,
'request', r => {
return !isFavicon(r);
}),
page.evaluate(() => {
return fetch(
'./post', {
method:
'POST',
body: JSON.stringify({foo:
'bar'}),
});
}),
]);
expect(request).toBeTruthy();
expect(request.hasPostData()).toBeTruthy();
expect(await request.fetchPostData()).toBe(
'{"foo":"bar"}');
});
it(
'should be |undefined| when there is no post data', async () => {
const {page, server} = await getTestState();
const response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.request().hasPostData()).toBeFalsy();
expect(await response.request().fetchPostData()).toBe(undefined);
});
it(
'should work with blobs', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
server.setRoute(
'/post', (_req, res) => {
return res.end();
});
const [request] = await Promise.all([
waitEvent<HTTPRequest>(page,
'request', r => {
return !isFavicon(r);
}),
page.evaluate(() => {
return fetch(
'./post', {
method:
'POST',
body:
new Blob([JSON.stringify({foo:
'bar'})], {
type:
'application/json',
}),
});
}),
]);
expect(request).toBeTruthy();
expect(request.hasPostData()).toBe(
true);
expect(await request.fetchPostData()).toBe(
'{"foo":"bar"}');
});
});
describe(
'Response.text',
function () {
it(
'should work', async () => {
const {page, server} = await getTestState();
const response = (await page.
goto(server.PREFIX +
'/simple.json'))!;
const responseText = (await response.text()).trimEnd();
expect(responseText).toBe(
'{"foo": "bar"}');
});
it(
'should return uncompressed text', async () => {
const {page, server} = await getTestState();
server.enableGzip(
'/simple.json');
const response = (await page.
goto(server.PREFIX +
'/simple.json'))!;
expect(response.headers()[
'content-encoding']).toBe(
'gzip');
const responseText = (await response.text()).trimEnd();
expect(responseText).toBe(
'{"foo": "bar"}');
});
it(
'should throw when requesting body of redirected response', async () => {
const {page, server} = await getTestState();
server.setRedirect(
'/foo.html',
'/empty.html');
const response = (await page.
goto(server.PREFIX +
'/foo.html'))!;
const redirectChain = response.request().redirectChain();
expect(redirectChain).toHaveLength(
1);
const redirected = redirectChain[
0]!.response()!;
expect(redirected.status()).toBe(
302);
let error!: Error;
await redirected.text().
catch(error_ => {
return (error = error_);
});
expect(error.message).toContain(
'Response body is unavailable for redirect responses',
);
});
it(
'should wait until response completes', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
// Setup server to trap request.
let serverResponse!: ServerResponse;
server.setRoute(
'/get', (_req, res) => {
serverResponse = res;
// In Firefox, |fetch| will be hanging until it receives |Content-Type| header
// from server.
res.setHeader(
'Content-Type',
'text/plain; charset=utf-8');
res.write(
'hello ');
});
// Setup page to trap response.
let requestFinished =
false;
page.on(
'requestfinished', r => {
return (requestFinished = requestFinished || r.url().includes(
'/get'));
});
// send request and wait for server response
const [pageResponse] = await Promise.all([
page.waitForResponse(r => {
return !isFavicon(r.request());
}),
page.evaluate(() => {
return fetch(
'./get', {method:
'GET'});
}),
server.waitForRequest(
'/get'),
]);
expect(serverResponse).toBeTruthy();
expect(pageResponse).toBeTruthy();
expect(pageResponse.status()).toBe(
200);
expect(requestFinished).toBe(
false);
const responseText = pageResponse.text();
// Write part of the response and wait for it to be flushed.
await
new Promise(x => {
return serverResponse.write(
'wor', x);
});
// Finish response.
await
new Promise<
void>(x => {
serverResponse.end(
'ld!', () => {
return x();
});
});
expect(await responseText).toBe(
'hello world!');
});
});
describe(
'Response.json',
function () {
it(
'should work', async () => {
const {page, server} = await getTestState();
const response = (await page.
goto(server.PREFIX +
'/simple.json'))!;
expect(await response.json()).toEqual({foo:
'bar'});
});
});
describe(
'Response.buffer',
function () {
it(
'should work', async () => {
const {page, server} = await getTestState();
const response = (await page.
goto(server.PREFIX +
'/pptr.png'))!;
const imageBuffer = fs.readFileSync(
path.join(
import.meta.dirname,
'../assets',
'pptr.png'),
);
const responseBuffer = await response.buffer();
expect(Buffer.from(responseBuffer).equals(imageBuffer)).toBe(
true);
});
it(
'should work with compression', async () => {
const {page, server} = await getTestState();
server.enableGzip(
'/pptr.png');
const response = (await page.
goto(server.PREFIX +
'/pptr.png'))!;
const imageBuffer = fs.readFileSync(
path.join(
import.meta.dirname,
'../assets',
'pptr.png'),
);
const responseBuffer = await response.buffer();
expect(Buffer.from(responseBuffer).equals(imageBuffer)).toBe(
true);
});
it(
'should throw if the response does not have a body', async () => {
const {page, server} = await getTestState();
await page.
goto(server.PREFIX +
'/empty.html');
server.setRoute(
'/test.html', (_req, res) => {
res.setHeader(
'Access-Control-Allow-Origin',
'*');
res.setHeader(
'Access-Control-Allow-Headers',
'x-ping');
res.end(
'Hello World');
});
const url = server.CROSS_PROCESS_PREFIX +
'/test.html';
const responsePromise = waitEvent<HTTPResponse>(
page,
'response',
response => {
// Get the preflight response.
return (
response.request().method() ===
'OPTIONS' && response.url() === url
);
},
);
// Trigger a request with a preflight.
await page.evaluate(async src => {
const response = await fetch(src, {
method:
'POST',
headers: {
'x-ping':
'pong'},
});
return response;
}, url);
const response = await responsePromise;
await expect(response.buffer()).rejects.toThrow(
'Could not load response body for this request. This might happen if the request is a preflight request.',
);
});
});
describe(
'Response.statusText',
function () {
it(
'should work', async () => {
const {page, server} = await getTestState();
server.setRoute(
'/cool', (_req, res) => {
res.writeHead(
200,
'cool!');
res.end();
});
const response = (await page.
goto(server.PREFIX +
'/cool'))!;
expect(response.statusText()).toBe(
'cool!');
});
it(
'handles missing status text', async () => {
const {page, server} = await getTestState();
server.setRoute(
'/nostatus', (_req, res) => {
res.writeHead(
200,
'');
res.end();
});
const response = (await page.
goto(server.PREFIX +
'/nostatus'))!;
expect(response.statusText()).toBe(
'');
});
});
describe(
'Response.timing',
function () {
it(
'returns timing information', async () => {
const {page, server} = await getTestState();
const responses: HTTPResponse[] = [];
page.on(
'response', response => {
if (isFavicon(response)) {
return;
}
return responses.push(response);
});
await page.
goto(server.EMPTY_PAGE);
expect(responses).toHaveLength(
1);
expect(responses[
0]!.timing()!.receiveHeadersEnd).toBeGreaterThan(
0);
});
});
describe(
'Network Events',
function () {
it(
'Page.Events.Request', async () => {
const {page, server} = await getTestState();
const requests: HTTPRequest[] = [];
page.on(
'request', request => {
return !isFavicon(request) && requests.push(request);
});
await page.
goto(server.EMPTY_PAGE);
expect(requests).toHaveLength(
1);
const request = requests[
0]!;
expect(request.url()).toBe(server.EMPTY_PAGE);
expect(request.method()).toBe(
'GET');
expect(request.response()).toBeTruthy();
expect(request.frame() === page.mainFrame()).toBe(
true);
expect(request.frame()!.url()).toBe(server.EMPTY_PAGE);
});
// Run this cache test both with a stylesheet and a script.
// Firefox currently does not handle network events for cached CSS files.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1879438
for (
const {html, resource, type} of [
{html:
'one-style.html', resource:
'one-style.css', type:
'stylesheet'},
{html:
'one-script.html', resource:
'one-script.js', type:
'script'},
]) {
it(`Page.Events.RequestServedFromCache
for ${type}`, async () => {
const {page, server} = await getTestState();
const cached: string[] = [];
page.on(
'requestservedfromcache', r => {
return !isFavicon(r) && cached.push(r.url().split(
'/').pop()!);
});
await page.
goto(server.PREFIX +
'/cached/' + html);
expect(cached).toEqual([]);
await
new Promise(res => {
setTimeout(res,
1000);
});
await page.reload();
expect(cached).toEqual([resource]);
});
}
it(
'Page.Events.Response', async () => {
const {page, server} = await getTestState();
const responses: HTTPResponse[] = [];
page.on(
'response', response => {
return !isFavicon(response) && responses.push(response);
});
await page.
goto(server.EMPTY_PAGE);
expect(responses).toHaveLength(
1);
const response = responses[
0]!;
expect(response.url()).toBe(server.EMPTY_PAGE);
expect(response.status()).toBe(
200);
expect(response.ok()).toBe(
true);
expect(response.request()).toBeTruthy();
});
it(
'Page.Events.RequestFailed', async () => {
const {page, server, isChrome} = await getTestState();
await page.setRequestInterception(
true);
page.on(
'request', request => {
if (request.url().endsWith(
'css')) {
void request.abort();
}
else {
void request.
continue();
}
});
const failedRequests: HTTPRequest[] = [];
page.on(
'requestfailed', request => {
return failedRequests.push(request);
});
await page.
goto(server.PREFIX +
'/one-style.html');
expect(failedRequests).toHaveLength(
1);
const failedRequest = failedRequests[
0]!;
expect(failedRequest.url()).toContain(
'one-style.css');
expect(failedRequest.response()).toBe(
null);
expect(failedRequest.frame()).toBeTruthy();
if (isChrome) {
expect(failedRequest.failure()!.errorText).toBe(
'net::ERR_FAILED');
}
else {
expect(failedRequest.failure()!.errorText).toBe(
'NS_ERROR_ABORT');
}
});
it(
'Page.Events.RequestFinished', async () => {
const {page, server} = await getTestState();
const requests: HTTPRequest[] = [];
page.on(
'requestfinished', request => {
return !isFavicon(request) && requests.push(request);
});
await page.
goto(server.EMPTY_PAGE);
expect(requests).toHaveLength(
1);
const request = requests[
0]!;
expect(request.url()).toBe(server.EMPTY_PAGE);
expect(request.response()).toBeTruthy();
expect(request.frame() === page.mainFrame()).toBe(
true);
expect(request.frame()!.url()).toBe(server.EMPTY_PAGE);
});
it(
'should fire events in proper order', async () => {
const {page, server} = await getTestState();
const events: string[] = [];
page.on(
'request', () => {
return events.push(
'request');
});
page.on(
'response', () => {
return events.push(
'response');
});
page.on(
'requestfinished', () => {
return events.push(
'requestfinished');
});
await page.
goto(server.EMPTY_PAGE);
// Events can sneak in after the page has navigate
expect(events.slice(
0,
3)).toEqual([
'request',
'response',
'requestfinished',
]);
});
it(
'should support redirects', async () => {
const {page, server} = await getTestState();
const events: string[] = [];
page.on(
'request', request => {
if (!isFavicon(request)) {
events.push(`${request.method()} ${request.url()}`);
}
});
page.on(
'response', response => {
if (!isFavicon(response)) {
events.push(`${response.status()} ${response.url()}`);
}
});
page.on(
'requestfinished', request => {
if (!isFavicon(request)) {
events.push(`DONE ${request.url()}`);
}
});
page.on(
'requestfailed', request => {
if (!isFavicon(request)) {
events.push(`FAIL ${request.url()}`);
}
});
server.setRedirect(
'/foo.html',
'/empty.html');
const FOO_URL = server.PREFIX +
'/foo.html';
const response = (await page.
goto(FOO_URL))!;
expect(events).toEqual([
`GET ${FOO_URL}`,
`
302 ${FOO_URL}`,
`DONE ${FOO_URL}`,
`GET ${server.EMPTY_PAGE}`,
`
200 ${server.EMPTY_PAGE}`,
`DONE ${server.EMPTY_PAGE}`,
]);
// Check redirect chain
const redirectChain = response.request().redirectChain();
expect(redirectChain).toHaveLength(
1);
expect(redirectChain[
0]!.url()).toContain(
'/foo.html');
});
});
describe(
'Request.isNavigationRequest', () => {
it(
'should work', async () => {
const {page, server} = await getTestState();
const requests =
new Map();
page.on(
'request', request => {
return requests.set(request.url().split(
'/').pop(), request);
});
server.setRedirect(
'/rrredirect',
'/frames/one-frame.html');
await page.
goto(server.PREFIX +
'/rrredirect');
expect(requests.get(
'rrredirect').isNavigationRequest()).toBe(
true);
expect(requests.get(
'one-frame.html').isNavigationRequest()).toBe(
true);
expect(requests.get(
'frame.html').isNavigationRequest()).toBe(
true);
expect(requests.get(
'script.js').isNavigationRequest()).toBe(
false);
expect(requests.get(
'style.css').isNavigationRequest()).toBe(
false);
});
it(
'should work with request interception', async () => {
const {page, server} = await getTestState();
const requests =
new Map();
page.on(
'request', request => {
requests.set(request.url().split(
'/').pop(), request);
void request.
continue();
});
await page.setRequestInterception(
true);
server.setRedirect(
'/rrredirect',
'/frames/one-frame.html');
await page.
goto(server.PREFIX +
'/rrredirect');
expect(requests.get(
'rrredirect').isNavigationRequest()).toBe(
true);
expect(requests.get(
'one-frame.html').isNavigationRequest()).toBe(
true);
expect(requests.get(
'frame.html').isNavigationRequest()).toBe(
true);
expect(requests.get(
'script.js').isNavigationRequest()).toBe(
false);
expect(requests.get(
'style.css').isNavigationRequest()).toBe(
false);
});
it(
'should work when navigating to image', async () => {
const {page, server} = await getTestState();
const [request] = await Promise.all([
waitEvent<HTTPRequest>(page,
'request'),
page.
goto(server.PREFIX +
'/pptr.png'),
]);
expect(request.isNavigationRequest()).toBe(
true);
});
});
describe(
'Page.setExtraHTTPHeaders',
function () {
it(
'should work', async () => {
const {page, server} = await getTestState();
await page.setExtraHTTPHeaders({
foo:
'bar',
});
const [request] = await Promise.all([
server.waitForRequest(
'/empty.html'),
page.
goto(server.EMPTY_PAGE),
]);
expect(request.headers[
'foo']).toBe(
'bar');
});
it(
'should throw for non-string header values', async () => {
const {page} = await getTestState();
let error!: Error;
try {
// @ts-expect-error purposeful bad input
await page.setExtraHTTPHeaders({foo:
1});
}
catch (error_) {
error = error_ as Error;
}
expect(error.message).toBe(
'Expected value of header "foo" to be String, but "number" is found.',
);
});
});
describe(
'Page.authenticate',
function () {
it(
'should work', async () => {
const {page, server} = await getTestState();
server.setAuth(
'/empty.html',
'user',
'pass');
await page.authenticate({
username:
'user',
password:
'pass',
});
const response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(
200);
});
it(
'should work with interception', async () => {
const {page, server} = await getTestState();
await page.setRequestInterception(
true);
page.on(
'request', async req => {
await req.
continue();
});
server.setAuth(
'/empty.html',
'user',
'pass');
await page.authenticate({
username:
'user',
password:
'pass',
});
const response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(
200);
});
it(
'should error if authentication is required but not enabled', async () => {
const {page, server} = await getTestState();
server.setAuth(
'/empty.html',
'user',
'pass');
let response;
try {
response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(
401);
}
catch (error) {
// In headful, an error is thrown instead of 401.
if (
!(error as Error).message?.includes(
'net::ERR_INVALID_AUTH_CREDENTIALS',
)
) {
throw error;
}
}
await page.authenticate({
username:
'user',
password:
'pass',
});
response = (await page.reload())!;
expect(response.status()).toBe(
200);
});
it(
'should fail if wrong credentials', async () => {
const {page, server} = await getTestState();
// Use unique user/password since Chrome caches credentials per origin.
server.setAuth(
'/empty.html',
'user2',
'pass2');
await page.authenticate({
username:
'foo',
password:
'bar',
});
const response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(
401);
});
it(
'should allow disable authentication', async () => {
const {page, server} = await getTestState();
// Use unique user/password since Chrome caches credentials per origin.
server.setAuth(
'/empty.html',
'user3',
'pass3');
await page.authenticate({
username:
'user3',
password:
'pass3',
});
let response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.status()).toBe(
200);
await page.authenticate(
null);
// Navigate to a different origin to bust Chrome's credential caching.
try {
response = (await page.
goto(
server.CROSS_PROCESS_PREFIX +
'/empty.html',
))!;
expect(response.status()).toBe(
401);
}
catch (error) {
// In headful, an error is thrown instead of 401.
if (
!(error as Error).message?.includes(
'net::ERR_INVALID_AUTH_CREDENTIALS',
)
) {
throw error;
}
}
});
// Run this cache test both with a stylesheet and a script.
// Firefox currently does not handle network events for cached CSS files.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1879438
for (
const {html, resource, type} of [
{html:
'one-style.html', resource:
'one-style.css', type:
'stylesheet'},
{html:
'one-script.html', resource:
'one-script.js', type:
'script'},
]) {
it(`should not disable caching
for ${type}`, async () => {
const {page, server} = await getTestState();
// Use unique user/password since Chrome caches credentials per origin.
const user = `user4-${type};`;
const pass = `pass4-${type};`;
server.setAuth(
'/cached/' + resource, user, pass);
server.setAuth(
'/cached/' + html, user, pass);
await page.authenticate({
username: user,
password: pass,
});
const responses =
new Map();
page.on(
'response', r => {
return responses.set(r.url().split(
'/').pop(), r);
});
// Load and re-load to make sure it's cached.
await page.
goto(server.PREFIX +
'/cached/' + html);
await page.reload();
expect(responses.get(resource).status()).toBe(
200);
expect(responses.get(resource).fromCache()).toBe(
true);
expect(responses.get(html).status()).toBe(
304);
expect(responses.get(html).fromCache()).toBe(
false);
});
}
});
describe(
'raw network headers', () => {
it(
'Same-origin set-cookie navigation', async () => {
const {page, server} = await getTestState();
const setCookieString =
'foo=bar';
server.setRoute(
'/empty.html', (_req, res) => {
res.setHeader(
'set-cookie', setCookieString);
res.end(
'hello world');
});
const response = (await page.
goto(server.EMPTY_PAGE))!;
expect(response.headers()[
'set-cookie']).toBe(setCookieString);
});
it(
'Same-origin set-cookie subresource', async () => {
const {page, server} = await getTestState();
await page.
goto(server.EMPTY_PAGE);
const setCookieString =
'foo=bar';
server.setRoute(
'/foo', (_req, res) => {
res.setHeader(
'set-cookie', setCookieString);
res.end(
'hello world');
});
const [response] = await Promise.all([
waitEvent<HTTPResponse>(page,
'response', res => {
return !isFavicon(res);
}),
page.evaluate(() => {
const xhr =
new XMLHttpRequest();
xhr.open(
'GET',
'/foo');
xhr.send();
}),
]);
expect(response.headers()[
'set-cookie']).toBe(setCookieString);
});
it(
'Cross-origin set-cookie', async () => {
const {page, httpsServer, close} = await launch(
{
acceptInsecureCerts:
true,
},
{
createContext:
true,
},
);
try {
await page.
goto(httpsServer.PREFIX +
'/empty.html');
const setCookieString =
'hello=world';
httpsServer.setRoute(
'/setcookie.html', (_req, res) => {
res.setHeader(
'Access-Control-Allow-Origin',
'*');
res.setHeader(
'set-cookie', setCookieString);
res.end();
});
await page.
goto(httpsServer.PREFIX +
'/setcookie.html');
const url = httpsServer.CROSS_PROCESS_PREFIX +
'/setcookie.html';
const [response] = await Promise.all([
waitEvent<HTTPResponse>(page,
'response', response => {
return response.url() === url;
}),
page.evaluate(src => {
const xhr =
new XMLHttpRequest();
xhr.open(
'GET', src);
xhr.send();
}, url),
]);
expect(response.headers()[
'set-cookie']).toBe(setCookieString);
}
finally {
await close();
}
});
});
describe(
'Page.setBypassServiceWorker', () => {
it(
'bypass for network', async () => {
const {page, server} = await getTestState();
const responses =
new Map();
page.on(
'response', r => {
return !isFavicon(r) && responses.set(r.url().split(
'/').pop(), r);
});
// Load and re-load to make sure serviceworker is installed and running.
await page.
goto(server.PREFIX +
'/serviceworkers/fetch/sw.html', {
waitUntil:
'networkidle2',
});
await page.evaluate(async () => {
return (globalThis as any).activationPromise;
});
await page.reload({
waitUntil:
'networkidle2',
});
expect(page.isServiceWorkerBypassed()).toBe(
false);
expect(responses.size).toBe(
2);
expect(responses.get(
'sw.html').status()).toBe(
200);
expect(responses.get(
'sw.html').fromServiceWorker()).toBe(
true);
expect(responses.get(
'style.css').status()).toBe(
200);
expect(responses.get(
'style.css').fromServiceWorker()).toBe(
true);
await page.setBypassServiceWorker(
true);
await page.reload({
waitUntil:
'networkidle2',
});
expect(page.isServiceWorkerBypassed()).toBe(
true);
expect(responses.get(
'sw.html').status()).toBe(
200);
expect(responses.get(
'sw.html').fromServiceWorker()).toBe(
false);
expect(responses.get(
'style.css').status()).toBe(
200);
expect(responses.get(
'style.css').fromServiceWorker()).toBe(
false);
});
});
describe(
'Request.resourceType', () => {
it(
'should work for document type', async () => {
const {page, server} = await getTestState();
const response = await page.
goto(server.EMPTY_PAGE);
const request = response!.request();
expect(request.resourceType()).toBe(
'document');
});
it(
'should work for stylesheets', async () => {
const {page, server} = await getTestState();
const cssRequests: HTTPRequest[] = [];
const promise =
new Promise<
void>(resolve => {
page.on(
'request', request => {
if (request.url().endsWith(
'css')) {
cssRequests.push(request);
resolve();
}
});
});
await page.
goto(server.PREFIX +
'/one-style.html');
await promise;
expect(cssRequests).toHaveLength(
1);
const request = cssRequests[
0]!;
expect(request.url()).toContain(
'one-style.css');
expect(request.resourceType()).toBe(
'stylesheet');
});
});
describe(
'Response.remoteAddress', () => {
it(
'should work', async () => {
const {page, server} = await getTestState();
const response = (await page.
goto(server.EMPTY_PAGE))!;
const remoteAddress = response.remoteAddress();
// Either IPv6 or IPv4, depending on environment.
expect(
remoteAddress.ip!.includes(
'::1') || remoteAddress.ip ===
'127.0.0.1',
).toBe(
true);
expect(remoteAddress.port).toBe(server.PORT);
});
it(
'should support redirects', async () => {
const {page, server} = await getTestState();
server.setRedirect(
'/foo.html',
'/empty.html');
const FOO_URL = server.PREFIX +
'/foo.html';
const response = (await page.
goto(FOO_URL))!;
// Check redirect chain
const redirectChain = response.request().redirectChain();
expect(redirectChain).toHaveLength(
1);
expect(redirectChain[
0]!.url()).toContain(
'/foo.html');
expect(redirectChain[
0]!.response()!.remoteAddress().port).toBe(
server.PORT,
);
});
});
});