Skip to content

Commit a96d6a7

Browse files
authored
feat: allow to pick stable channel (#5817)
1 parent 0d32b05 commit a96d6a7

File tree

13 files changed

+137
-6
lines changed

13 files changed

+137
-6
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ jobs:
283283
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx folio test/ --workers=1 --forbid-only --timeout=60000 --global-timeout=5400000 --retries=3 --reporter=dot,json -p video"
284284
env:
285285
BROWSER: "chromium"
286-
CRPATH: "/opt/google/chrome/chrome"
286+
PW_CHROMIUM_CHANNEL: "chrome"
287287
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
288288
- uses: actions/upload-artifact@v1
289289
if: ${{ always() }}

docs/src/api/class-browsertype.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,19 @@ Whether to run browser in headless mode. More details for
178178
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to `true` unless the
179179
[`option: devtools`] option is `true`.
180180

181+
### option: BrowserType.launch.channel
182+
- `channel` <[string]>
183+
184+
Chromium distribution channel, one of
185+
* chrome
186+
* chrome-beta
187+
* chrome-dev
188+
* chrome-canary
189+
* msedge
190+
* msedge-beta
191+
* msedge-dev
192+
* msedge-canary
193+
181194
### option: BrowserType.launch.executablePath
182195
- `executablePath` <[path]>
183196

src/cli/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ program
103103

104104
program
105105
.command('install-deps [browserType...]')
106-
.description('install dependencies necessary to run browser')
106+
.description('install dependencies necessary to run browsers (will ask for sudo permissions)')
107107
.action(async function(browserType) {
108108
try {
109109
await installDeps(browserType);
110110
} catch (e) {
111-
console.log(`Failed to install browsers\n${e}`);
111+
console.log(`Failed to install browser dependencies\n${e}`);
112112
process.exit(1);
113113
}
114114
});

src/client/connection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export class Connection {
8282
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject }));
8383
} catch (e) {
8484
const innerStack = ((process.env.PWDEBUGIMPL || isUnderTest()) && e.stack) ? e.stack.substring(e.stack.indexOf(e.message) + e.message.length) : '';
85-
e.stack = e.message + innerStack + stack;
85+
e.stack = e.message + innerStack + '\n' + stack;
8686
throw e;
8787
}
8888
}

src/protocol/channels.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ export interface BrowserTypeChannel extends Channel {
217217
connectOverCDP(params: BrowserTypeConnectOverCDPParams, metadata?: Metadata): Promise<BrowserTypeConnectOverCDPResult>;
218218
}
219219
export type BrowserTypeLaunchParams = {
220+
channel?: string,
220221
executablePath?: string,
221222
args?: string[],
222223
ignoreAllDefaultArgs?: boolean,
@@ -240,6 +241,7 @@ export type BrowserTypeLaunchParams = {
240241
slowMo?: number,
241242
};
242243
export type BrowserTypeLaunchOptions = {
244+
channel?: string,
243245
executablePath?: string,
244246
args?: string[],
245247
ignoreAllDefaultArgs?: boolean,

src/protocol/protocol.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ BrowserType:
300300

301301
launch:
302302
parameters:
303+
channel: string?
303304
executablePath: string?
304305
args:
305306
type: array?

src/protocol/validator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
154154
contentScript: tOptional(tBoolean),
155155
});
156156
scheme.BrowserTypeLaunchParams = tObject({
157+
channel: tOptional(tString),
157158
executablePath: tOptional(tString),
158159
args: tOptional(tArray(tString)),
159160
ignoreAllDefaultArgs: tOptional(tBoolean),

src/server/browserType.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export abstract class BrowserType extends SdkObject {
5151
this._registry = playwrightOptions.registry;
5252
}
5353

54-
executablePath(): string {
54+
executablePath(options?: types.LaunchOptions): string {
5555
return this._registry.executablePath(this._name) || '';
5656
}
5757

@@ -165,7 +165,7 @@ export abstract class BrowserType extends SdkObject {
165165
else
166166
browserArguments.push(...this._defaultArgs(options, isPersistent, userDataDir));
167167

168-
const executable = executablePath || this.executablePath();
168+
const executable = executablePath || this.executablePath(options);
169169
if (!executable)
170170
throw new Error(`No executable path is specified. Pass "executablePath" option directly.`);
171171
if (!(await existsAsync(executable))) {

src/server/chromium/chromium.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { ProgressController } from '../progress';
3131
import { TimeoutSettings } from '../../utils/timeoutSettings';
3232
import { helper } from '../helper';
3333
import { CallMetadata } from '../instrumentation';
34+
import { findChromiumChannel } from './findChromiumChannel';
3435

3536
export class Chromium extends BrowserType {
3637
private _devtools: CRDevTools | undefined;
@@ -42,6 +43,12 @@ export class Chromium extends BrowserType {
4243
this._devtools = this._createDevTools();
4344
}
4445

46+
executablePath(options?: types.LaunchOptions): string {
47+
if (options?.channel)
48+
return findChromiumChannel(options.channel);
49+
return super.executablePath(options);
50+
}
51+
4552
async connectOverCDP(metadata: CallMetadata, wsEndpoint: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number) {
4653
const controller = new ProgressController(metadata, this);
4754
controller.setLogName('browser');
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the 'License');
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an 'AS IS' BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import fs from 'fs';
18+
import path from 'path';
19+
20+
function darwin(channel: string): string | undefined {
21+
switch (channel) {
22+
case 'chrome': return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
23+
case 'chrome-canary': return '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';
24+
case 'msedge': return '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge';
25+
case 'msedge-beta': return '/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta';
26+
case 'msedge-dev': return '/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev';
27+
case 'msedge-canary': return '/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary';
28+
}
29+
}
30+
31+
function linux(channel: string): string | undefined {
32+
switch (channel) {
33+
case 'chrome': return '/opt/google/chrome/chrome';
34+
case 'chrome-beta': return '/opt/google/chrome-beta/chrome';
35+
case 'chrome-dev': return '/opt/google/chrome-unstable/chrome';
36+
case 'msedge-dev': return '/opt/microsoft/msedge-dev/msedge';
37+
}
38+
}
39+
40+
function win32(channel: string): string | undefined {
41+
let suffix: string | undefined;
42+
switch (channel) {
43+
case 'chrome': suffix = `\\Google\\Chrome\\Application\\chrome.exe`; break;
44+
case 'chrome-canary': suffix = `\\Google\\Chrome SxS\\Application\\chrome.exe`; break;
45+
case 'msedge': suffix = `\\Microsoft\\Edge\\Application\\msedge.exe`; break;
46+
case 'msedge-beta': suffix = `\\Microsoft\\Edge Beta\\Application\\msedge.exe`; break;
47+
case 'msedge-dev': suffix = `\\Microsoft\\Edge Dev\\Application\\msedge.exe`; break;
48+
case 'msedge-canary': suffix = `\\Microsoft\\Edge SxS\\Application\\msedge.exe`; break;
49+
}
50+
if (!suffix)
51+
return;
52+
const prefixes = [
53+
process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)']
54+
].filter(Boolean) as string[];
55+
56+
let result: string | undefined;
57+
prefixes.forEach(prefix => {
58+
const chromePath = path.join(prefix, suffix!);
59+
if (canAccess(chromePath))
60+
result = chromePath;
61+
});
62+
return result;
63+
}
64+
65+
function canAccess(file: string) {
66+
if (!file)
67+
return false;
68+
69+
try {
70+
fs.accessSync(file);
71+
return true;
72+
} catch (e) {
73+
return false;
74+
}
75+
}
76+
77+
export function findChromiumChannel(channel: string): string {
78+
let result: string | undefined;
79+
if (process.platform === 'linux')
80+
result = linux(channel);
81+
else if (process.platform === 'win32')
82+
result = win32(channel);
83+
else if (process.platform === 'darwin')
84+
result = darwin(channel);
85+
86+
if (!result)
87+
throw new Error(`Chromium distribution '${channel}' is not supported on ${process.platform}`);
88+
89+
if (canAccess(result))
90+
return result;
91+
throw new Error(`Chromium distribution was not found: ${channel}`);
92+
}

0 commit comments

Comments
 (0)