cherry-pick(#31914): chore: various roll fixes for .NET

This commit is contained in:
Max Schmitt 2024-07-30 19:10:50 +02:00
parent 468b9b1e7a
commit dfecfa5be1
5 changed files with 57 additions and 9 deletions

View File

@ -503,6 +503,12 @@ If set changes the request URL. New URL must have same protocol as original one.
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
Defaults to `20`. Pass `0` to not follow redirects.
### option: Route.fetch.maxRetries
* since: v1.46
- `maxRetries` <[int]>
Maximum number of times network errors should be retried. Currently only `ECONNRESET` error is retried. Does not retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
### option: Route.fetch.timeout
* since: v1.33
- `timeout` <[float]>

View File

@ -524,9 +524,9 @@ Does not enforce fixed viewport, allows resizing window in the headed mode.
## context-option-clientCertificates
- `clientCertificates` <[Array]<[Object]>>
- `origin` <[string]> Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port.
- `certPath` ?<[string]> Path to the file with the certificate in PEM format.
- `keyPath` ?<[string]> Path to the file with the private key in PEM format.
- `pfxPath` ?<[string]> Path to the PFX or PKCS12 encoded private key and certificate chain.
- `certPath` ?<[path]> Path to the file with the certificate in PEM format.
- `keyPath` ?<[path]> Path to the file with the private key in PEM format.
- `pfxPath` ?<[path]> Path to the PFX or PKCS12 encoded private key and certificate chain.
- `passphrase` ?<[string]> Passphrase for the private key (PEM or PFX).
TLS Client Authentication allows the server to request a client certificate and verify it.

View File

@ -19570,6 +19570,12 @@ export interface Route {
*/
maxRedirects?: number;
/**
* Maximum number of times network errors should be retried. Currently only `ECONNRESET` error is retried. Does not
* retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
*/
maxRetries?: number;
/**
* If set changes the request method (e.g. GET or POST).
*/

View File

@ -17,9 +17,15 @@
import os from 'os';
import * as util from 'util';
import { getPlaywrightVersion } from '../../packages/playwright-core/lib/utils/userAgent';
import { expect, playwrightTest as it } from '../config/browserTest';
import { expect, playwrightTest as base } from '../config/browserTest';
import { kTargetClosedErrorMessage } from 'tests/config/errors';
const it = base.extend({
context: async () => {
throw new Error('global fetch tests should not use context');
}
});
it.skip(({ mode }) => mode !== 'default');
for (const method of ['fetch', 'delete', 'get', 'head', 'patch', 'post', 'put'] as const) {
@ -33,9 +39,11 @@ for (const method of ['fetch', 'delete', 'get', 'head', 'patch', 'post', 'put']
expect(response.headers()['content-type']).toBe('application/json; charset=utf-8');
expect(response.headersArray()).toContainEqual({ name: 'Content-Type', value: 'application/json; charset=utf-8' });
expect(await response.text()).toBe('head' === method ? '' : '{"foo": "bar"}\n');
await request.dispose();
});
}
it(`should dispose global request`, async function({ playwright, server }) {
const request = await playwright.request.newContext();
const response = await request.get(server.PREFIX + '/simple.json');
@ -43,6 +51,7 @@ it(`should dispose global request`, async function({ playwright, server }) {
await request.dispose();
const error = await response.body().catch(e => e);
expect(error.message).toContain('Response has been disposed');
await request.dispose();
});
it('should support global userAgent option', async ({ playwright, server }) => {
@ -54,6 +63,7 @@ it('should support global userAgent option', async ({ playwright, server }) => {
expect(response.ok()).toBeTruthy();
expect(response.url()).toBe(server.EMPTY_PAGE);
expect(serverRequest.headers['user-agent']).toBe('My Agent');
await request.dispose();
});
it('should support global timeout option', async ({ playwright, server }) => {
@ -61,6 +71,7 @@ it('should support global timeout option', async ({ playwright, server }) => {
server.setRoute('/empty.html', (req, res) => {});
const error = await request.get(server.EMPTY_PAGE).catch(e => e);
expect(error.message).toContain('Request timed out after 100ms');
await request.dispose();
});
it('should propagate extra http headers with redirects', async ({ playwright, server }) => {
@ -76,6 +87,7 @@ it('should propagate extra http headers with redirects', async ({ playwright, se
expect(req1.headers['my-secret']).toBe('Value');
expect(req2.headers['my-secret']).toBe('Value');
expect(req3.headers['my-secret']).toBe('Value');
await request.dispose();
});
it('should support global httpCredentials option', async ({ playwright, server }) => {
@ -96,6 +108,7 @@ it('should return error with wrong credentials', async ({ playwright, server })
const request = await playwright.request.newContext({ httpCredentials: { username: 'user', password: 'wrong' } });
const response = await request.get(server.EMPTY_PAGE);
expect(response.status()).toBe(401);
await request.dispose();
});
it('should work with correct credentials and matching origin', async ({ playwright, server }) => {
@ -103,6 +116,7 @@ it('should work with correct credentials and matching origin', async ({ playwrig
const request = await playwright.request.newContext({ httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX } });
const response = await request.get(server.EMPTY_PAGE);
expect(response.status()).toBe(200);
await request.dispose();
});
it('should work with correct credentials and matching origin case insensitive', async ({ playwright, server }) => {
@ -110,6 +124,7 @@ it('should work with correct credentials and matching origin case insensitive',
const request = await playwright.request.newContext({ httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase() } });
const response = await request.get(server.EMPTY_PAGE);
expect(response.status()).toBe(200);
await request.dispose();
});
it('should return error with correct credentials and mismatching scheme', async ({ playwright, server }) => {
@ -117,6 +132,7 @@ it('should return error with correct credentials and mismatching scheme', async
const request = await playwright.request.newContext({ httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.replace('http://', 'https://') } });
const response = await request.get(server.EMPTY_PAGE);
expect(response.status()).toBe(401);
await request.dispose();
});
it('should return error with correct credentials and mismatching hostname', async ({ playwright, server }) => {
@ -126,6 +142,7 @@ it('should return error with correct credentials and mismatching hostname', asyn
const request = await playwright.request.newContext({ httpCredentials: { username: 'user', password: 'pass', origin: origin } });
const response = await request.get(server.EMPTY_PAGE);
expect(response.status()).toBe(401);
await request.dispose();
});
it('should return error with correct credentials and mismatching port', async ({ playwright, server }) => {
@ -134,6 +151,7 @@ it('should return error with correct credentials and mismatching port', async ({
const request = await playwright.request.newContext({ httpCredentials: { username: 'user', password: 'pass', origin: origin } });
const response = await request.get(server.EMPTY_PAGE);
expect(response.status()).toBe(401);
await request.dispose();
});
it('should support WWW-Authenticate: Basic', async ({ playwright, server }) => {
@ -152,6 +170,7 @@ it('should support WWW-Authenticate: Basic', async ({ playwright, server }) => {
const response = await request.get(server.EMPTY_PAGE);
expect(response.status()).toBe(200);
expect(credentials).toBe('user:pass');
await request.dispose();
});
it('should support HTTPCredentials.send', async ({ playwright, server }) => {
@ -176,12 +195,14 @@ it('should support HTTPCredentials.send', async ({ playwright, server }) => {
expect(serverRequest.headers.authorization).toBe(undefined);
expect(response.status()).toBe(200);
}
await request.dispose();
});
it('should support global ignoreHTTPSErrors option', async ({ playwright, httpsServer }) => {
const request = await playwright.request.newContext({ ignoreHTTPSErrors: true });
const response = await request.get(httpsServer.EMPTY_PAGE);
expect(response.status()).toBe(200);
await request.dispose();
});
it('should propagate ignoreHTTPSErrors on redirects', async ({ playwright, httpsServer }) => {
@ -189,12 +210,14 @@ it('should propagate ignoreHTTPSErrors on redirects', async ({ playwright, https
const request = await playwright.request.newContext();
const response = await request.get(httpsServer.PREFIX + '/redir', { ignoreHTTPSErrors: true });
expect(response.status()).toBe(200);
await request.dispose();
});
it('should resolve url relative to global baseURL option', async ({ playwright, server }) => {
const request = await playwright.request.newContext({ baseURL: server.PREFIX });
const response = await request.get('/empty.html');
expect(response.url()).toBe(server.EMPTY_PAGE);
await request.dispose();
});
it('should set playwright as user-agent', async ({ playwright, server, isWindows, isLinux, isMac }) => {
@ -221,12 +244,14 @@ it('should set playwright as user-agent', async ({ playwright, server, isWindows
expect(userAgentMasked.replace(/<ARCH>; \w+ [^)]+/, '<ARCH>; distro version')).toBe('Playwright/X.X.X (<ARCH>; distro version) node/X.X' + suffix);
else if (isMac)
expect(userAgentMasked).toBe('Playwright/X.X.X (<ARCH>; macOS X.X) node/X.X' + suffix);
await request.dispose();
});
it('should be able to construct with context options', async ({ playwright, browserType, server }) => {
const request = await playwright.request.newContext((browserType as any)._defaultContextOptions);
const response = await request.get(server.EMPTY_PAGE);
expect(response.ok()).toBeTruthy();
await request.dispose();
});
it('should return empty body', async ({ playwright, server }) => {
@ -254,6 +279,7 @@ it('should abort requests when context is disposed', async ({ playwright, server
expect(result.message).toContain(kTargetClosedErrorMessage);
}
await connectionClosed;
await request.dispose();
});
it('should abort redirected requests when context is disposed', async ({ playwright, server }) => {
@ -269,6 +295,7 @@ it('should abort redirected requests when context is disposed', async ({ playwri
expect(result instanceof Error).toBeTruthy();
expect(result.message).toContain(kTargetClosedErrorMessage);
await connectionClosed;
await request.dispose();
});
it('should remove content-length from redirected post requests', async ({ playwright, server }) => {
@ -473,7 +500,6 @@ it('should serialize post data on the client', async ({ playwright, server }) =>
await postReq;
const body = await (await serverReq).postBody;
expect(body.toString()).toBe('{"foo":"bar"}');
// expect(serverRequest.rawHeaders).toContain('vaLUE');
await request.dispose();
});
@ -486,7 +512,8 @@ it('should throw after dispose', async ({ playwright, server }) => {
it('should retry ECONNRESET', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30978' }
}, async ({ context, server }) => {
}, async ({ playwright, server }) => {
const request = await playwright.request.newContext();
let requestCount = 0;
server.setRoute('/test', (req, res) => {
if (requestCount++ < 3) {
@ -496,8 +523,9 @@ it('should retry ECONNRESET', {
res.writeHead(200, { 'content-type': 'text/plain' });
res.end('Hello!');
});
const response = await context.request.fetch(server.PREFIX + '/test', { maxRetries: 3 });
const response = await request.fetch(server.PREFIX + '/test', { maxRetries: 3 });
expect(response.status()).toBe(200);
expect(await response.text()).toBe('Hello!');
expect(requestCount).toBe(4);
await request.dispose();
});

View File

@ -400,10 +400,18 @@ function generateNameDefault(member, name, t, parent) {
if (names[2] === names[1])
names.pop(); // get rid of duplicates, cheaply
let attemptedName = names.pop();
const typesDiffer = function(left, right) {
const typesDiffer = function(/** @type {Documentation.Type} */ left, /** @type {Documentation.Type} */ right) {
if (left.expression && right.expression)
return left.expression !== right.expression;
return JSON.stringify(right.properties) !== JSON.stringify(left.properties);
const toExpression = (/** @type {Documentation.Member} */ t) => t.name + t.type?.expression;
const leftOverRightProperties = new Set(left.properties?.map(toExpression) ?? []);
for (const prop of right.properties ?? []) {
const expression = toExpression(prop);
if (!leftOverRightProperties.has(expression))
return true;
leftOverRightProperties.delete(expression);
}
return leftOverRightProperties.size > 0;
};
while (true) {
// crude attempt at removing plurality