UNPKG

11.2 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
5 * This code may only be used under the BSD style license found at
6 * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
7 * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
8 * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
9 * Google as part of the polymer project is also subject to an additional IP
10 * rights grant found at http://polymer.github.io/PATENTS.txt
11 */
12var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13 if (k2 === undefined) k2 = k;
14 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
15}) : (function(o, m, k, k2) {
16 if (k2 === undefined) k2 = k;
17 o[k2] = m[k];
18}));
19var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20 Object.defineProperty(o, "default", { enumerable: true, value: v });
21}) : function(o, v) {
22 o["default"] = v;
23});
24var __importStar = (this && this.__importStar) || function (mod) {
25 if (mod && mod.__esModule) return mod;
26 var result = {};
27 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
28 __setModuleDefault(result, mod);
29 return result;
30};
31Object.defineProperty(exports, "__esModule", { value: true });
32exports.openAndSwitchToNewTab = exports.makeDriver = exports.validateBrowserConfig = exports.parseBrowserConfigString = exports.browserSignature = exports.fcpBrowsers = exports.supportedBrowsers = void 0;
33const webdriver = __importStar(require("selenium-webdriver"));
34const chrome = __importStar(require("selenium-webdriver/chrome"));
35const edge = __importStar(require("selenium-webdriver/edge"));
36const firefox = __importStar(require("selenium-webdriver/firefox"));
37const install_1 = require("./install");
38const util_1 = require("./util");
39/** Browsers we can drive. */
40exports.supportedBrowsers = new Set([
41 'chrome',
42 'firefox',
43 'safari',
44 'edge',
45 'ie',
46]);
47// Note that the edgedriver package doesn't work on recent versions of
48// Windows 10, so users must manually install following Microsoft's
49// documentation.
50const browserWebdriverModules = new Map([
51 ['chrome', 'chromedriver'],
52 ['firefox', 'geckodriver'],
53 ['ie', 'iedriver'],
54]);
55/** Cases where Tachometer's browser name scheme does not equal WebDriver's. */
56const webdriverBrowserNames = new Map([
57 ['edge', 'MicrosoftEdge'],
58 ['ie', 'internet explorer'],
59]);
60/** Browsers that support headless mode. */
61const headlessBrowsers = new Set(['chrome', 'firefox']);
62/** Browsers for which we can find the first contentful paint (FCP) time. */
63exports.fcpBrowsers = new Set(['chrome']);
64/**
65 * Create a deterministic unique string key for the given BrowserConfig.
66 */
67function browserSignature(config) {
68 var _a, _b, _c, _d, _e, _f;
69 return JSON.stringify([
70 config.name,
71 config.headless,
72 (_a = config.remoteUrl) !== null && _a !== void 0 ? _a : '',
73 config.windowSize.width,
74 config.windowSize.height,
75 (_b = config.binary) !== null && _b !== void 0 ? _b : '',
76 (_c = config.addArguments) !== null && _c !== void 0 ? _c : [],
77 (_d = config.removeArguments) !== null && _d !== void 0 ? _d : [],
78 (_e = config.cpuThrottlingRate) !== null && _e !== void 0 ? _e : 1,
79 (_f = config.preferences) !== null && _f !== void 0 ? _f : {},
80 ]);
81}
82exports.browserSignature = browserSignature;
83/**
84 * Parse and validate a browser string specification. Examples:
85 *
86 * chrome
87 * chrome-headless
88 * chrome@<remote-selenium-server>
89 */
90function parseBrowserConfigString(str) {
91 let remoteUrl;
92 const at = str.indexOf('@');
93 if (at !== -1) {
94 remoteUrl = str.substring(at + 1);
95 str = str.substring(0, at);
96 }
97 const headless = str.endsWith('-headless');
98 if (headless === true) {
99 str = str.replace(/-headless$/, '');
100 }
101 const name = str;
102 const config = { name, headless };
103 if (remoteUrl !== undefined) {
104 config.remoteUrl = remoteUrl;
105 }
106 return config;
107}
108exports.parseBrowserConfigString = parseBrowserConfigString;
109/**
110 * Throw if any property of the given BrowserConfig is invalid.
111 */
112function validateBrowserConfig({ name, headless, remoteUrl, windowSize, }) {
113 if (!exports.supportedBrowsers.has(name)) {
114 throw new Error(`Browser ${name} is not supported, ` +
115 `only ${[...exports.supportedBrowsers].join(', ')} are currently supported.`);
116 }
117 if (headless === true && !headlessBrowsers.has(name)) {
118 throw new Error(`Browser ${name} does not support headless mode.`);
119 }
120 if (remoteUrl !== undefined && !util_1.isHttpUrl(remoteUrl)) {
121 throw new Error(`Invalid browser remote URL "${remoteUrl}".`);
122 }
123 if (windowSize.width < 0 || windowSize.height < 0) {
124 throw new Error(`Invalid window size, width and height must be >= 0.`);
125 }
126}
127exports.validateBrowserConfig = validateBrowserConfig;
128/**
129 * Configure a WebDriver suitable for benchmarking the given browser.
130 */
131async function makeDriver(config) {
132 const browserName = config.name;
133 const webdriverModuleName = browserWebdriverModules.get(browserName);
134 if (webdriverModuleName != null) {
135 await install_1.installOnDemand(webdriverModuleName);
136 require(webdriverModuleName);
137 }
138 const builder = new webdriver.Builder();
139 const webdriverName = webdriverBrowserNames.get(config.name) || config.name;
140 builder.forBrowser(webdriverName);
141 builder.setChromeOptions(chromeOpts(config));
142 builder.setFirefoxOptions(firefoxOpts(config));
143 if (config.remoteUrl !== undefined) {
144 builder.usingServer(config.remoteUrl);
145 }
146 else if (config.name === 'edge') {
147 // There appears to be bug where WebDriver doesn't automatically start or
148 // find an Edge service and throws "Cannot read property 'start' of null"
149 // so we need to start the service ourselves.
150 // See https://stackoverflow.com/questions/48577924.
151 // tslint:disable-next-line:no-any TODO setEdgeService function is missing.
152 builder.setEdgeService(new edge.ServiceBuilder());
153 }
154 const driver = await builder.build();
155 if (config.name === 'safari' || config.name === 'edge' ||
156 config.name === 'ie') {
157 // Safari, Edge, and IE don't have flags we can use to launch with a given
158 // window size, but webdriver can resize the window after we've started up.
159 // Some versions of Safari have a bug where it is required to also provide
160 // an x/y position (see https://github.com/SeleniumHQ/selenium/issues/3796).
161 const rect = config.name === 'safari' ? Object.assign(Object.assign({}, config.windowSize), { x: 0, y: 0 }) :
162 config.windowSize;
163 await driver.manage().window().setRect(rect);
164 }
165 return driver;
166}
167exports.makeDriver = makeDriver;
168function chromeOpts(config) {
169 const opts = new chrome.Options();
170 if (config.binary) {
171 opts.setChromeBinaryPath(config.binary);
172 }
173 if (config.headless === true) {
174 opts.addArguments('--headless');
175 }
176 if (config.addArguments) {
177 opts.addArguments(...config.addArguments);
178 }
179 if (config.removeArguments) {
180 opts.excludeSwitches(...config.removeArguments);
181 }
182 const { width, height } = config.windowSize;
183 opts.addArguments(`--window-size=${width},${height}`);
184 return opts;
185}
186function firefoxOpts(config) {
187 const opts = new firefox.Options();
188 if (config.preferences) {
189 for (const [name, value] of Object.entries(config.preferences)) {
190 opts.setPreference(name, value);
191 }
192 }
193 if (config.binary) {
194 opts.setBinary(config.binary);
195 }
196 if (config.headless === true) {
197 // tslint:disable-next-line:no-any TODO Incorrect types.
198 opts.addArguments('-headless');
199 }
200 const { width, height } = config.windowSize;
201 // tslint:disable-next-line:no-any TODO Incorrect types.
202 opts.addArguments(`-width=${width}`);
203 // tslint:disable-next-line:no-any TODO Incorrect types.
204 opts.addArguments(`-height=${height}`);
205 return opts;
206}
207/**
208 * Open a new tab and switch to it. Assumes that the driver is on a page that
209 * hasn't replaced `window.open` (e.g. the initial blank tab that we always
210 * switch back to after running a benchmark).
211 */
212async function openAndSwitchToNewTab(driver, config) {
213 // Chrome and Firefox add new tabs to the end of the handle list, but Safari
214 // adds them to the beginning. Just look for the new one instead of making
215 // any assumptions about this order.
216 const tabsBefore = await driver.getAllWindowHandles();
217 if (tabsBefore.length !== 1) {
218 throw new Error(`Expected only 1 open tab, got ${tabsBefore.length}`);
219 }
220 // "noopener=yes" prevents the new window from being able to access the
221 // first window. We set that here because in Chrome (and perhaps other
222 // browsers) we see a very significant improvement in the reliability of
223 // measurements, in particular it appears to eliminate interference between
224 // code across runs. It is likely this flag increases process isolation in a
225 // way that prevents code caching across tabs.
226 await driver.executeScript('window.open("", "", "noopener=yes");');
227 // Firefox (and maybe other browsers) won't always report the new tab ID
228 // immediately, so we'll need to poll for it.
229 const maxRetries = 20;
230 const retrySleepMs = 250;
231 let retries = 0;
232 let newTabId;
233 while (true) {
234 const tabsAfter = await driver.getAllWindowHandles();
235 const newTabs = tabsAfter.filter((tab) => tab !== tabsBefore[0]);
236 if (newTabs.length === 1) {
237 newTabId = newTabs[0];
238 break;
239 }
240 retries++;
241 if (newTabs.length > 1 || retries > maxRetries) {
242 throw new Error(`Expected to create 1 new tab, got ${newTabs.length}`);
243 }
244 await new Promise((resolve) => setTimeout(resolve, retrySleepMs));
245 }
246 await driver.switchTo().window(newTabId);
247 if (config.name === 'ie' || config.name === 'safari') {
248 // For IE and Safari (with rel=noopener) we get a new window instead of a
249 // new tab, so we need to resize every time.
250 const rect = config.name === 'safari' ? Object.assign(Object.assign({}, config.windowSize), { x: 0, y: 0 }) :
251 config.windowSize;
252 await driver.manage().window().setRect(rect);
253 }
254 const driverWithSendDevToolsCommand = driver;
255 if (driverWithSendDevToolsCommand.sendDevToolsCommand &&
256 config.cpuThrottlingRate !== undefined) {
257 // Enables CPU throttling to emulate slow CPUs.
258 await driverWithSendDevToolsCommand.sendDevToolsCommand('Emulation.setCPUThrottlingRate', { rate: config.cpuThrottlingRate });
259 }
260}
261exports.openAndSwitchToNewTab = openAndSwitchToNewTab;
262//# sourceMappingURL=browser.js.map
\No newline at end of file