Skip to content

Commit 954f238

Browse files
committed
feat(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 78b73b9 commit 954f238

File tree

5 files changed

+311
-10
lines changed

5 files changed

+311
-10
lines changed

lib/provider/chromium.spec-int.ts

Lines changed: 24 additions & 0 deletions
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

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

lib/provider/utils/cloud_storage_xml.ts

Lines changed: 1 addition & 2 deletions
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

Lines changed: 6 additions & 7 deletions
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

Lines changed: 1 addition & 1 deletion
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)