Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ability to enable new Chrome headless mode #875

Merged
merged 1 commit into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

здесь просто разнес тесты в разные дескрайбы и для headless дописал проверку на строку

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);
});
});
});
Loading