Skip to content

Commit e9fe663

Browse files
authored
cherry-pick(#21104): chore: consolidate http/https fetching (#21284)
Fixes #21227 Fixes #20784 Supersedes #21076
1 parent 249825f commit e9fe663

File tree

7 files changed

+54
-40
lines changed

7 files changed

+54
-40
lines changed

packages/playwright-core/src/server/chromium/chromium.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,22 @@ import { Browser } from '../browser';
3333
import type * as types from '../types';
3434
import type * as channels from '@protocol/channels';
3535
import type { HTTPRequestParams } from '../../utils/network';
36-
import { NET_DEFAULT_TIMEOUT } from '../../utils/network';
3736
import { fetchData } from '../../utils/network';
3837
import { getUserAgent } from '../../utils/userAgent';
3938
import { wrapInASCIIBox } from '../../utils/ascii';
40-
import { debugMode, headersArrayToObject, } from '../../utils';
39+
import { debugMode, headersArrayToObject, } from '../../utils';
4140
import { removeFolders } from '../../utils/fileUtils';
4241
import { RecentLogsCollector } from '../../common/debugLogger';
4342
import type { Progress } from '../progress';
4443
import { ProgressController } from '../progress';
4544
import { TimeoutSettings } from '../../common/timeoutSettings';
4645
import { helper } from '../helper';
4746
import type { CallMetadata } from '../instrumentation';
48-
import http from 'http';
49-
import https from 'https';
47+
import type http from 'http';
5048
import { registry } from '../registry';
5149
import { ManualPromise } from '../../utils/manualPromise';
5250
import { validateBrowserContextOptions } from '../browserContext';
5351
import { chromiumSwitches } from './chromiumSwitches';
54-
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '../happy-eyeballs';
5552

5653
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
5754

@@ -338,21 +335,11 @@ async function urlToWSEndpoint(progress: Progress, endpointURL: string) {
338335
return endpointURL;
339336
progress.log(`<ws preparing> retrieving websocket url from ${endpointURL}`);
340337
const httpURL = endpointURL.endsWith('/') ? `${endpointURL}json/version/` : `${endpointURL}/json/version/`;
341-
const isHTTPS = endpointURL.startsWith('https://');
342-
const json = await new Promise<string>((resolve, reject) => {
343-
(isHTTPS ? https : http).get(httpURL, {
344-
timeout: NET_DEFAULT_TIMEOUT,
345-
agent: isHTTPS ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent,
346-
}, resp => {
347-
if (resp.statusCode! < 200 || resp.statusCode! >= 400) {
348-
reject(new Error(`Unexpected status ${resp.statusCode} when connecting to ${httpURL}.\n` +
349-
`This does not look like a DevTools server, try connecting via ws://.`));
350-
}
351-
let data = '';
352-
resp.on('data', chunk => data += chunk);
353-
resp.on('end', () => resolve(data));
354-
}).on('error', reject);
355-
});
338+
const json = await fetchData({
339+
url: httpURL,
340+
}, async (_, resp) => new Error(`Unexpected status ${resp.statusCode} when connecting to ${httpURL}.\n` +
341+
`This does not look like a DevTools server, try connecting via ws://.`)
342+
);
356343
return JSON.parse(json).webSocketDebuggerUrl;
357344
}
358345

packages/playwright-core/src/server/fetch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { HttpsProxyAgent, SocksProxyAgent } from '../utilsBundle';
3030
import { BrowserContext } from './browserContext';
3131
import { CookieStore, domainMatches } from './cookieStore';
3232
import { MultipartFormData } from './formData';
33-
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from './happy-eyeballs';
33+
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '../utils/happy-eyeballs';
3434
import type { CallMetadata } from './instrumentation';
3535
import { SdkObject } from './instrumentation';
3636
import type { Playwright } from './playwright';
@@ -69,7 +69,7 @@ export type APIRequestFinishedEvent = {
6969
body?: Buffer;
7070
};
7171

72-
export type SendRequestOptions = https.RequestOptions & {
72+
type SendRequestOptions = https.RequestOptions & {
7373
maxRedirects: number,
7474
deadline: number,
7575
__testHookLookup?: (hostname: string) => LookupAddress[]

packages/playwright-core/src/server/transport.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import type { WebSocket } from '../utilsBundle';
2020
import type { ClientRequest, IncomingMessage } from 'http';
2121
import type { Progress } from './progress';
2222
import { makeWaitForNextTask } from '../utils';
23-
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from './happy-eyeballs';
23+
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '../utils/happy-eyeballs';
2424

2525
export type ProtocolRequest = {
2626
id: number;

packages/playwright-core/src/server/happy-eyeballs.ts renamed to packages/playwright-core/src/utils/happy-eyeballs.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import * as http from 'http';
1919
import * as https from 'https';
2020
import * as net from 'net';
2121
import * as tls from 'tls';
22-
import { ManualPromise } from '../utils/manualPromise';
23-
import type { SendRequestOptions } from './fetch';
22+
import { ManualPromise } from './manualPromise';
2423

2524
// Implementation(partial) of Happy Eyeballs 2 algorithm described in
2625
// https://www.rfc-editor.org/rfc/rfc8305
@@ -50,7 +49,7 @@ export const httpsHappyEyeballsAgent = new HttpsHappyEyeballsAgent();
5049
export const httpHappyEyeballsAgent = new HttpHappyEyeballsAgent();
5150

5251
async function createConnectionAsync(options: http.ClientRequestArgs, oncreate: ((err: Error | null, socket?: net.Socket) => void) | undefined, useTLS: boolean) {
53-
const lookup = (options as SendRequestOptions).__testHookLookup || lookupAddresses;
52+
const lookup = (options as any).__testHookLookup || lookupAddresses;
5453
const hostname = clientRequestArgsToHostName(options);
5554
const addresses = await lookup(hostname);
5655
const sockets = new Set<net.Socket>();

packages/playwright-core/src/utils/network.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import * as URL from 'url';
2424
import type { URLMatch } from '../common/types';
2525
import { isString, isRegExp } from './rtti';
2626
import { globToRegex } from './glob';
27+
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from './happy-eyeballs';
2728

2829
export async function createSocket(host: string, port: number): Promise<net.Socket> {
2930
return new Promise((resolve, reject) => {
@@ -39,15 +40,22 @@ export type HTTPRequestParams = {
3940
headers?: http.OutgoingHttpHeaders,
4041
data?: string | Buffer,
4142
timeout?: number,
43+
rejectUnauthorized?: boolean,
4244
};
4345

4446
export const NET_DEFAULT_TIMEOUT = 30_000;
4547

4648
export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.IncomingMessage) => void, onError: (error: Error) => void) {
4749
const parsedUrl = URL.parse(params.url);
48-
let options: https.RequestOptions = { ...parsedUrl };
49-
options.method = params.method || 'GET';
50-
options.headers = params.headers;
50+
let options: https.RequestOptions = {
51+
...parsedUrl,
52+
agent: parsedUrl.protocol === 'https:' ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent,
53+
method: params.method || 'GET',
54+
headers: params.headers,
55+
};
56+
if (params.rejectUnauthorized !== undefined)
57+
options.rejectUnauthorized = params.rejectUnauthorized;
58+
5159
const timeout = params.timeout ?? NET_DEFAULT_TIMEOUT;
5260

5361
const proxyURL = getProxyForUrl(params.url);

packages/playwright-test/src/plugins/webServerPlugin.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import http from 'http';
17-
import https from 'https';
1816
import path from 'path';
1917
import net from 'net';
2018

2119
import { debug } from 'playwright-core/lib/utilsBundle';
22-
import { raceAgainstTimeout, launchProcess } from 'playwright-core/lib/utils';
20+
import { raceAgainstTimeout, launchProcess, httpRequest } from 'playwright-core/lib/utils';
2321

2422
import type { FullConfig, Reporter } from '../../types/testReporter';
2523
import type { TestRunnerPlugin } from '.';
@@ -159,20 +157,18 @@ async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onStdErr: Re
159157
}
160158

161159
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onStdErr: Reporter['onStdErr']): Promise<number> {
162-
const commonRequestOptions = { headers: { Accept: '*/*' } };
163-
const isHttps = url.protocol === 'https:';
164-
const requestOptions = isHttps ? {
165-
...commonRequestOptions,
166-
rejectUnauthorized: !ignoreHTTPSErrors,
167-
} : commonRequestOptions;
168160
return new Promise(resolve => {
169161
debugWebServer(`HTTP GET: ${url}`);
170-
(isHttps ? https : http).get(url, requestOptions, res => {
162+
httpRequest({
163+
url: url.toString(),
164+
headers: { Accept: '*/*' },
165+
rejectUnauthorized: !ignoreHTTPSErrors
166+
}, res => {
171167
res.resume();
172168
const statusCode = res.statusCode ?? 0;
173169
debugWebServer(`HTTP Status: ${statusCode}`);
174170
resolve(statusCode);
175-
}).on('error', error => {
171+
}, error => {
176172
if ((error as NodeJS.ErrnoException).code === 'DEPTH_ZERO_SELF_SIGNED_CERT')
177173
onStdErr?.(`[WebServer] Self-signed certificate detected. Try adding ignoreHTTPSErrors: true to config.webServer.`);
178174
debugWebServer(`Error while checking if ${url} is available: ${error.message}`);

tests/playwright-test/web-server.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,3 +608,27 @@ test('should treat 3XX as available server', async ({ runInlineTest }, { workerI
608608
expect(result.output).toContain('[WebServer] listening');
609609
expect(result.output).toContain('[WebServer] error from server');
610610
});
611+
612+
test('should check ipv4 and ipv6 with happy eyeballs when URL is passed', async ({ runInlineTest }, { workerIndex }) => {
613+
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/20784' });
614+
const port = workerIndex * 2 + 10500;
615+
const result = await runInlineTest({
616+
'test.spec.ts': `
617+
import { test, expect } from '@playwright/test';
618+
test('pass', async ({}) => {});
619+
`,
620+
'playwright.config.ts': `
621+
module.exports = {
622+
webServer: {
623+
command: 'node -e "require(\\'http\\').createServer((req, res) => res.end()).listen(${port}, \\'127.0.0.1\\')"',
624+
url: 'http://localhost:${port}/',
625+
}
626+
};
627+
`,
628+
}, {}, { DEBUG: 'pw:webserver' });
629+
expect(result.exitCode).toBe(0);
630+
expect(result.passed).toBe(1);
631+
expect(result.output).toContain('Process started');
632+
expect(result.output).toContain(`HTTP GET: http://localhost:${port}/`);
633+
expect(result.output).toContain('WebServer available');
634+
});

0 commit comments

Comments
 (0)