Skip to content

Commit

Permalink
feat: add ability to enable new Chrome headless mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sipayRT committed Mar 18, 2024
1 parent 03d40dd commit 850785f
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 44 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1234,7 +1234,7 @@ Cloud service access key or secret key. Default value is `null`.
Ability to choose different datacenters for run in cloud service. Default value is `null`.

#### headless
Ability to run headless browser in cloud service. Default value is `null`.
Ability to run headless browser in cloud service. Default value is `null`. Can be set as a Boolean (the default value of the browser will be used). For Chrome browsers starting from version 112 also can be specified as a string with "new"|"old" values - this will enable the new headless mode (see [Chrome's blog post](https://developer.chrome.com/docs/chromium/new-headless)).

#### isolation
Ability to execute tests in isolated clean-state environment ([incognito browser context](https://chromedevtools.github.io/devtools-protocol/tot/Target/#method-createBrowserContext)). It means that `testsPerSession` can be set to `Infinity` in order to speed up tests execution and save browser resources. Currently works only in chrome@93 and higher. Default value is `null`, but `true` for chrome@93 and higher.
Expand Down
14 changes: 9 additions & 5 deletions src/browser/new-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@ const DEFAULT_PORT = 4444;
const headlessBrowserOptions = {
chrome: {
capabilityName: "goog:chromeOptions",
args: ["headless", "disable-gpu"],
getArgs: headlessMode => {
const headlessValue = _.isBoolean(headlessMode) ? "headless" : `headless=${headlessMode}`;

return [headlessValue, "disable-gpu"];
},
},
firefox: {
capabilityName: "moz:firefoxOptions",
args: ["-headless"],
getArgs: () => ["-headless"],
},
msedge: {
capabilityName: "ms:edgeOptions",
args: ["--headless"],
getArgs: () => ["--headless"],
},
edge: {
capabilityName: "ms:edgeOptions",
args: ["--headless"],
getArgs: () => ["--headless"],
},
};

Expand Down Expand Up @@ -138,7 +142,7 @@ module.exports = class NewBrowser extends Browser {
const browserCapabilities = capabilities[capabilitySettings.capabilityName] ?? {};
capabilities[capabilitySettings.capabilityName] = {
...browserCapabilities,
args: [...(browserCapabilities.args ?? []), ...capabilitySettings.args],
args: [...(browserCapabilities.args ?? []), ...capabilitySettings.getArgs(headless)],
};
return capabilities;
}
Expand Down
17 changes: 16 additions & 1 deletion src/config/browser-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,22 @@ function buildBrowserOptions(defaultFactory, extra) {
user: options.optionalString("user"),
key: options.optionalString("key"),
region: options.optionalString("region"),
headless: options.optionalBoolean("headless"),
headless: option({
defaultValue: defaultFactory("headless"),
validate: value => {
if (_.isNull(value) || _.isBoolean(value)) {
return;
}

if (typeof value !== "string") {
throw new Error('"headless" option should be boolean or string with "new" or "old" values');
}

if (value !== "old" && value !== "new") {
throw new Error(`"headless" option should be "new" or "old", but got "${value}"`);
}
},
}),

isolation: option({
defaultValue: defaultFactory("isolation"),
Expand Down
2 changes: 1 addition & 1 deletion src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export interface CommonConfig {
headers: Record<string, string> | null;

system: SystemConfig;
headless: boolean | null;
headless: "old" | "new" | boolean | null;
isolation: boolean;

openAndWaitOpts: {
Expand Down
36 changes: 26 additions & 10 deletions test/src/browser/new-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,33 @@ describe("NewBrowser", () => {
});

describe("headless setting", () => {
it("should generate browser specific settings - chrome", async () => {
await mkBrowser_({
headless: true,
desiredCapabilities: { browserName: "chrome" },
}).init();
describe("chrome", () => {
it("should generate browser specific settings", async () => {
await mkBrowser_({
headless: true,
desiredCapabilities: { browserName: "chrome" },
}).init();

assert.calledWithMatch(webdriverio.remote, {
capabilities: {
browserName: "chrome",
"goog:chromeOptions": { args: ["headless", "disable-gpu"] },
},
});
});

assert.calledWithMatch(webdriverio.remote, {
capabilities: {
browserName: "chrome",
"goog:chromeOptions": { args: ["headless", "disable-gpu"] },
},
it("should add passed value to args if string was passed", async () => {
await mkBrowser_({
headless: "new",
desiredCapabilities: { browserName: "chrome" },
}).init();

assert.calledWithMatch(webdriverio.remote, {
capabilities: {
browserName: "chrome",
"goog:chromeOptions": { args: ["headless=new", "disable-gpu"] },
},
});
});
});

Expand Down
97 changes: 71 additions & 26 deletions test/src/config/browser-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -1736,40 +1736,85 @@ describe("config browser-options", () => {
});
});

["strictSSL", "headless"].forEach(option => {
describe(option, () => {
it(`should throw error if ${option} is not a null or boolean`, () => {
const readConfig = _.set({}, "browsers.b1", mkBrowser_({ [option]: "string" }));
describe("strictSSL", () => {
it(`should throw error if option is not a null or boolean`, () => {
const readConfig = _.set({}, "browsers.b1", mkBrowser_({ strictSSL: "string" }));

Config.read.returns(readConfig);
Config.read.returns(readConfig);

assert.throws(() => createConfig(), Error, `"${option}" must be a boolean`);
});
assert.throws(() => createConfig(), Error, `"strictSSL" must be a boolean`);
});

it("should set a default value if it is not set in config", () => {
const readConfig = _.set({}, "browsers.b1", mkBrowser_());
Config.read.returns(readConfig);
it("should set a default value if it is not set in config", () => {
const readConfig = _.set({}, "browsers.b1", mkBrowser_());
Config.read.returns(readConfig);

const config = createConfig();
const config = createConfig();

assert.equal(config[option], defaults[option]);
});
assert.equal(config.strictSSL, defaults.strictSSL);
});

it(`should override ${option} option`, () => {
const readConfig = {
[option]: false,
browsers: {
b1: mkBrowser_(),
b2: mkBrowser_({ [option]: true }),
},
};
Config.read.returns(readConfig);
it(`should override "strictSSL" option`, () => {
const readConfig = {
strictSSL: false,
browsers: {
b1: mkBrowser_(),
b2: mkBrowser_({ strictSSL: true }),
},
};
Config.read.returns(readConfig);

const config = createConfig();
const config = createConfig();

assert.isFalse(config.browsers.b1[option]);
assert.isTrue(config.browsers.b2[option]);
});
assert.isFalse(config.browsers.b1.strictSSL);
assert.isTrue(config.browsers.b2.strictSSL);
});
});

describe("headless", () => {
it("should throw error if option is not a null, boolean or string", () => {
const readConfig = _.set({}, "browsers.b1", mkBrowser_({ headless: { a: 1 } }));

Config.read.returns(readConfig);

assert.throws(
() => createConfig(),
Error,
'"headless" option should be boolean or string with "new" or "old" values',
);
});

it("should throw error if option is string with invalid values", () => {
const readConfig = _.set({}, "browsers.b1", mkBrowser_({ headless: "some" }));

Config.read.returns(readConfig);

assert.throws(() => createConfig(), Error, '"headless" option should be "new" or "old", but got "some"');
});

it("should set a default value if it is not set in config", () => {
const readConfig = _.set({}, "browsers.b1", mkBrowser_());
Config.read.returns(readConfig);

const config = createConfig();

assert.equal(config.headless, defaults.headless);
});

it("should override 'headless' option", () => {
const readConfig = {
headless: false,
browsers: {
b1: mkBrowser_(),
b2: mkBrowser_({ headless: true }),
},
};
Config.read.returns(readConfig);

const config = createConfig();

assert.isFalse(config.browsers.b1.headless);
assert.isTrue(config.browsers.b2.headless);
});
});
});

0 comments on commit 850785f

Please sign in to comment.