Skip to content

Commit 0b8679b

Browse files
authoredJun 5, 2019
feat(chromium): download the chromium binary based on a major version (#368)
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 78b73b9 commit 0b8679b

File tree

5 files changed

+310
-10
lines changed

5 files changed

+310
-10
lines changed
 

Diff for: ‎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+
});

Diff for: ‎lib/provider/chromium.ts

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

Diff for: ‎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

Diff for: ‎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
/**

Diff for: ‎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)
Please sign in to comment.