Skip to content

Commit 0b23461

Browse files
committed
chore(chromium): download the chromium binary based on a major version
Experimental feature to download the chromium browser based on the ChromeDriver version. This might be help to prevent browser / browser driver mismatch when downloading ChromeDriver. In the new scenario, you could download ChromeDriver 73.x.x.x and Chromium 73.
1 parent 289d183 commit 0b23461

File tree

5 files changed

+327
-10
lines changed

5 files changed

+327
-10
lines changed

lib/provider/chromium.spec-int.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {Chromium} from './chromium';
2+
import * as loglevel from 'loglevel';
3+
4+
const log = loglevel.getLogger('webdriver-manager');
5+
log.setLevel('debug');
6+
7+
describe('chromium', () => {
8+
describe('class Chromium', () => {
9+
10+
beforeAll(() => {
11+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
12+
});
13+
14+
it('should download a config', async () => {
15+
const chromium = new Chromium({});
16+
chromium.osType = 'Darwin'
17+
const majorVersion = '73';
18+
const allJson = await chromium.downloadAllJson();
19+
const versionJson = await chromium.downloadVersionJson(allJson, majorVersion);
20+
const storageJson = await chromium.downloadStorageObject(versionJson, majorVersion);
21+
await chromium.downloadUrl(storageJson, majorVersion);
22+
});
23+
});
24+
});

lib/provider/chromium.ts

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
import * as fs from 'fs';
2+
import * as os from 'os';
3+
import * as path from 'path';
4+
import * as semver from 'semver';
5+
import { convertXmlToVersionList, updateXml } from './utils/cloud_storage_xml';
6+
import { initOptions, requestBody, JsonObject, requestBinary } from './utils/http_utils';
7+
import { changeFilePermissions, generateConfigFile, getBinaryPathFromConfig, removeFiles, renameFileWithVersion, unzipFile, zipFileList } from './utils/file_utils';
8+
import { isExpired } from './utils/file_utils';
9+
import { OUT_DIR, ProviderConfig, ProviderInterface } from './provider';
10+
11+
export class Chromium implements ProviderInterface {
12+
cacheFileName = 'chromium-all.json';
13+
cacheVersionFileName = 'chromium-version.json';
14+
cacheStorageFileName = 'chromium-storage.json';
15+
compressedBinaryFileName = 'chromium.zip';
16+
configFileName = 'chromium.config.json';
17+
ignoreSSL = false;
18+
osType = os.type();
19+
osArch = os.arch();
20+
outDir = OUT_DIR;
21+
proxy: string = null;
22+
23+
constructor(providerConfig?: ProviderConfig) {
24+
if (!providerConfig) {
25+
return;
26+
}
27+
if (providerConfig.cacheFileName) {
28+
this.cacheFileName = providerConfig.cacheFileName;
29+
}
30+
if (providerConfig.configFileName) {
31+
this.configFileName = providerConfig.configFileName;
32+
}
33+
this.ignoreSSL = providerConfig.ignoreSSL;
34+
if (providerConfig.osArch) {
35+
this.osArch = providerConfig.osArch;
36+
}
37+
if (providerConfig.osType) {
38+
this.osType = providerConfig.osType;
39+
}
40+
if (providerConfig.outDir) {
41+
this.outDir = providerConfig.outDir;
42+
}
43+
if (providerConfig.proxy) {
44+
this.proxy = providerConfig.proxy;
45+
}
46+
}
47+
48+
private makeDirectory(fileName: string) {
49+
const dir = path.dirname(fileName);
50+
try {
51+
fs.mkdirSync(dir);
52+
} catch (err) {
53+
}
54+
}
55+
56+
/**
57+
* Step 1: Download the json file that contains all the releases by OS. Each
58+
* OS will have a list of release versions. The requested body will also be
59+
* written to the out directory.
60+
*
61+
* The requested url is https://omahaproxy.appspot.com/all.json. Some other
62+
* urls include a timestamped csv https://omahaproxy.appspot.com/history.
63+
* @return Promise of the all-json file.
64+
*/
65+
async downloadAllJson(): Promise<JsonObject> {
66+
const fileName = path.resolve(this.outDir, this.cacheFileName);
67+
if (!isExpired(fileName)) {
68+
return JSON.parse(fs.readFileSync(fileName).toString());
69+
} else {
70+
this.makeDirectory(fileName);
71+
const httpOptions = { fileName, ignoreSSL: this.ignoreSSL,
72+
proxy: this.proxy };
73+
74+
if (isExpired(fileName)) {
75+
const allJsonUrl = 'https://omahaproxy.appspot.com/all.json';
76+
let contents = await requestBody(allJsonUrl, httpOptions);
77+
contents = `{ "all": ${contents} }`;
78+
const jsonObj = JSON.parse(contents);
79+
fs.writeFileSync(fileName, JSON.stringify(jsonObj, null, 2));
80+
return jsonObj;
81+
} else {
82+
const contents = fs.readFileSync(fileName).toString();
83+
return JSON.parse(contents);
84+
}
85+
}
86+
}
87+
88+
/**
89+
* Step 2: From the all-json object, make a request that matches the major
90+
* version requested. The requested body will also be written to file in the
91+
* out directory.
92+
*
93+
* An example of a requsted url is
94+
* https://omahaproxy.appspot.com/deps.json?version=72.0.3626.81
95+
* @param allJson The all-json object.
96+
* @param majorVersion The major version, this must be a whole number.
97+
*/
98+
async downloadVersionJson(allJson: JsonObject, majorVersion: string
99+
): Promise<JsonObject> {
100+
const fileName = path.resolve(this.outDir,
101+
this.cacheVersionFileName.replace('.json', `-${majorVersion}.json`));
102+
if (!isExpired(fileName)) {
103+
return JSON.parse(fs.readFileSync(fileName).toString());
104+
} else {
105+
this.makeDirectory(fileName);
106+
107+
// Look up a version that makes sense.
108+
const all = allJson['all'];
109+
let os = '';
110+
if (this.osType === 'Windows_NT') {
111+
os = 'win';
112+
if (this.osArch === 'x64') {
113+
os = 'win64';
114+
}
115+
} else if (this.osType === 'Linux') {
116+
os = 'linux';
117+
} else {
118+
os = 'mac';
119+
}
120+
121+
let workingFullVersion = '';
122+
let workingSemanticVersion = '0.0.0';
123+
for (let item of all) {
124+
if (item['os'] === os) {
125+
const versions = item['versions'];
126+
for (let version of versions) {
127+
const fullVersion = version['current_version'];
128+
const major = fullVersion.split('.')[0];
129+
const minor = fullVersion.split('.')[1];
130+
const patch = fullVersion.split('.')[2];
131+
const semanticVersion = `${major}.${minor}.${patch}`;
132+
if (majorVersion === major) {
133+
if (semver.gt(semanticVersion, workingSemanticVersion)) {
134+
workingFullVersion = fullVersion;
135+
}
136+
}
137+
}
138+
}
139+
}
140+
141+
// Make a request and write it out to file.
142+
const httpOptions = { fileName, ignoreSSL: this.ignoreSSL,
143+
proxy: this.proxy };
144+
145+
const depsUrl = 'https://omahaproxy.appspot.com/deps.json?version=' +
146+
workingFullVersion;
147+
const contents = await requestBody(depsUrl, httpOptions);
148+
const jsonObj = JSON.parse(contents);
149+
fs.writeFileSync(fileName, JSON.stringify(jsonObj, null, 2));
150+
return jsonObj;
151+
}
152+
}
153+
154+
/**
155+
* Step 3: From the downloaded-version-json object, get the revision number.
156+
* This is the "chromium_base_position" and make a request to the storage
157+
* bucket. If the returned value is {"kind": "storage#objects"}, then
158+
* decrement the revision number.
159+
*
160+
* An example is the chromium_base_position revision number (612437).
161+
* https://www.googleapis.com/storage/v1/b/chromium-browser-snapshots/o?delimiter=/&prefix=Linux_x64/612437/
162+
* returns {"kind": "storage#objects"}.
163+
*
164+
* We keep decrementing the number until we reach 612434 where there is a list
165+
* of items.
166+
* @param downloadJson The download-version-json object.
167+
* @param majorVersion The major version, this must be a whole number.
168+
*/
169+
async downloadStorageObject(downloadJson: JsonObject, majorVersion: string
170+
): Promise<JsonObject> {
171+
const fileName = path.resolve(this.outDir,
172+
this.cacheStorageFileName.replace('.json', `-${majorVersion}.json`));
173+
if (!isExpired(fileName)) {
174+
return JSON.parse(fs.readFileSync(fileName).toString());
175+
} else {
176+
this.makeDirectory(fileName);
177+
let revisionUrl = 'https://www.googleapis.com/storage/v1/b/' +
178+
'chromium-browser-snapshots/o?delimiter=/&prefix=';
179+
let os = '';
180+
if (this.osType === 'Windows_NT') {
181+
os = 'Win';
182+
if (this.osArch === 'x64') {
183+
os = 'Win_x64';
184+
}
185+
} else if (this.osType === 'Linux') {
186+
os = 'Linux';
187+
if (this.osArch === 'x64') {
188+
os = 'Linux_x64';
189+
}
190+
} else {
191+
os = 'Mac';
192+
}
193+
revisionUrl += os + '/';
194+
let chromiumBasePosition: number = downloadJson['chromium_base_position'];
195+
196+
const httpOptions = { fileName, ignoreSSL: this.ignoreSSL,
197+
proxy: this.proxy };
198+
while(chromiumBasePosition > 0) {
199+
const revisionBasePositionUrl =
200+
`${revisionUrl}${chromiumBasePosition}/`;
201+
const body = await requestBody(revisionBasePositionUrl, httpOptions);
202+
const jsonBody = JSON.parse(body);
203+
if (jsonBody['items']) {
204+
fs.writeFileSync(fileName, JSON.stringify(jsonBody, null, 2));
205+
return jsonBody;
206+
} else {
207+
chromiumBasePosition--;
208+
}
209+
}
210+
return null;
211+
}
212+
}
213+
214+
/**
215+
* Step 4: Get the download url for the chromium zip. Unzipping the zip file
216+
* directory. The folders and binaries uncompressed are different for each OS.
217+
* The following is examples of each OS:
218+
*
219+
* downloads/
220+
* |- chrome-linux/chrome
221+
* |- chrome-mac/Chromium.app
222+
* |- chrome-win/chrome.exe
223+
*
224+
* @param storageObject The download-storage-json object
225+
* @param majorVersion The major version, this must be a whole number.
226+
*/
227+
async downloadUrl(storageObject: JsonObject, majorVersion: string
228+
): Promise<void> {
229+
const fileName = path.resolve(this.outDir,
230+
this.compressedBinaryFileName.replace('.zip', `-${majorVersion}.zip`));
231+
if (isExpired(fileName)) {
232+
const httpOptions = { fileName, ignoreSSL: this.ignoreSSL,
233+
proxy: this.proxy };
234+
for (let item of storageObject['items'] as JsonObject[]) {
235+
const name: string = item['name'];
236+
if (name.indexOf('chrome') >= 0) {
237+
const downloadUrl = item['mediaLink'];
238+
await requestBinary(downloadUrl, httpOptions);
239+
break;
240+
}
241+
}
242+
}
243+
unzipFile(fileName, this.outDir);
244+
}
245+
246+
async updateBinary(majorVersion?: string): Promise<void> {
247+
const allJson = await this.downloadAllJson();
248+
const downloadVersionJson = await this.downloadVersionJson(
249+
allJson, majorVersion);
250+
const storageObject = await this.downloadStorageObject(
251+
downloadVersionJson, majorVersion);
252+
await this.downloadUrl(storageObject, majorVersion);
253+
}
254+
255+
getBinaryPath(version?: string): string | null {
256+
try {
257+
const configFilePath = path.resolve(this.outDir, this.configFileName);
258+
return getBinaryPathFromConfig(configFilePath, version);
259+
} catch (_) {
260+
return null;
261+
}
262+
}
263+
264+
getStatus(): string | null {
265+
return '';
266+
}
267+
268+
cleanFiles(): string {
269+
return removeFiles(this.outDir, [/chromium.*/g]);
270+
}
271+
}
272+
273+
/**
274+
* Helps translate the os type and arch to the download name associated
275+
* with composing the download link.
276+
* @param ostype The operating stystem type.
277+
* @param osarch The chip architecture.
278+
* @returns The download name associated with composing the download link.
279+
*/
280+
export function osHelper(ostype: string, osarch: string): string {
281+
if (ostype === 'Darwin') {
282+
return 'Mac';
283+
} else if (ostype === 'Windows_NT') {
284+
if (osarch === 'x64') {
285+
return 'Win_x64';
286+
} else if (osarch === 'x32') {
287+
return 'Win';
288+
}
289+
} else if (ostype === 'Linux') {
290+
if (osarch === 'x64') {
291+
return 'Linux_64';
292+
}
293+
}
294+
return null;
295+
}

lib/provider/utils/cloud_storage_xml.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
33
import * as semver from 'semver';
4-
import {convertXml2js, readXml} from './file_utils';
5-
import {isExpired} from './file_utils';
4+
import {convertXml2js, isExpired, readXml} from './file_utils';
65
import {HttpOptions, JsonObject, requestBody} from './http_utils';
76
import {VersionList} from './version_list';
87

lib/provider/utils/file_utils.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export function unzipFile(zipFileName: string, dstDir: string): string[] {
105105
const zip = new AdmZip(zipFileName);
106106
zip.extractAllTo(dstDir, true);
107107
for (const fileItem of zipFileList(zipFileName)) {
108+
console.log(fileItem);
108109
fileList.push(path.resolve(dstDir, fileItem));
109110
}
110111
return fileList;
@@ -115,18 +116,16 @@ export function unzipFile(zipFileName: string, dstDir: string): string[] {
115116
* @param tarball The tarball file.
116117
* @returns A lsit of files in the tarball file.
117118
*/
118-
export function tarFileList(tarball: string): Promise<string[]> {
119+
export async function tarFileList(tarball: string): Promise<string[]> {
119120
const fileList: string[] = [];
120-
return tar
121+
await tar
121122
.list({
122123
file: tarball,
123124
onentry: entry => {
124125
fileList.push(entry['path'].toString());
125126
}
126-
})
127-
.then(() => {
128-
return fileList;
129-
});
127+
});
128+
return fileList;
130129
}
131130

132131
/**
@@ -183,7 +182,7 @@ export function generateConfigFile(
183182
configData['last'] = lastFileBinaryPath;
184183
}
185184
configData['all'] = getMatchingFiles(outDir, fileBinaryPathRegex);
186-
fs.writeFileSync(fileName, JSON.stringify(configData));
185+
fs.writeFileSync(fileName, JSON.stringify(configData, null, 2));
187186
}
188187

189188
/**

spec/jasmine-int.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"spec_dir": "dist",
33
"spec_files": [
4-
"**/*.spec-int.js"
4+
"**/chromium.spec-int.js"
55
],
66
"stopSpecOnExpectationFailure": false,
77
"random": false

0 commit comments

Comments
 (0)