Skip to content

Commit 7544ca8

Browse files
authored
Merge pull request #247 from Anthony-YY/support_download_inspect
support chrome download activity access under headless mode
2 parents b2f535a + 4db4169 commit 7544ca8

File tree

17 files changed

+274
-8
lines changed

17 files changed

+274
-8
lines changed

.eslintrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ ringcentralPreset.extends = ringcentralPreset.extends.map((item) => {
1515

1616
module.exports = {
1717
...ringcentralPreset,
18+
globals: {
19+
chrome: 'writable',
20+
},
1821
env: {
1922
...ringcentralPreset.env,
2023
es6: true,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"require" : "../../utils/ts-mocha.js",
3+
"watch-extensions": "ts",
4+
"timeout": 30000,
5+
"reporter": "dot",
6+
"spec": "test/**/*.spec.ts"
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
test
3+
mocha.opts
4+
tsconfig.json
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
test
3+
mocha.opts
4+
tsconfig.json
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
save-exact=true
2+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# @testring/dwnld-collector-crx
2+
3+
## Installation
4+
5+
```bash
6+
npm install @testring/dwnld-collector-crx
7+
```
8+
9+
## How to use
10+
accessing chrome internal page like chrome://downloads is not allowed in headless mode, as a result, checking download results becomes unavaiable.
11+
once this chrome extension installed. chrome download items can be accessed within page via localStorage, like this:
12+
```javascript
13+
const downloadsJSONStr = await browser.execute(() => {
14+
return localStorage.getItem('_DOWNLOADS_');
15+
})
16+
// the result is already sort ASC by startTime
17+
const downloads = JSON.parse(downloadsJSONStr);
18+
19+
```
20+
downloads is an array of download items, each item has following properties:
21+
```javascript
22+
{
23+
fileName: 'example.pdf',
24+
filePath: '/Users/username/Downloads/example.pdf',
25+
state: 'complete',
26+
startTime: '2021-01-01T00:00:00.000Z',
27+
state: 'complete',
28+
}
29+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
declare module '@testring/dwnld-collector-crx' {
2+
export const getCrxBase64: () => string;
3+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "@testring/dwnld-collector-crx",
3+
"version": "0.6.11",
4+
"main": "./dist/index.js",
5+
"types": "./index.d.ts",
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/ringcentral/testring.git"
9+
},
10+
"author": "RingCentral",
11+
"license": "MIT",
12+
"scripts": {
13+
"postinstall": "mkdir -p dist && crx pack src/extension -o dist/extension.crx"
14+
},
15+
"dependencies": {
16+
"crx": "5.0.1"
17+
}
18+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
chrome.downloads.onCreated.addListener((downloadItem) => {
2+
chrome.tabs.query({}, (tabs) => {
3+
tabs.forEach((tab) => {
4+
chrome.tabs.sendMessage(tab.id, {
5+
action: 'downloadStarted',
6+
downloadItem,
7+
});
8+
});
9+
});
10+
});
11+
12+
chrome.downloads.onDeterminingFilename.addListener((downloadItem) => {
13+
chrome.tabs.query({}, (tabs) => {
14+
tabs.forEach((tab) => {
15+
chrome.tabs.sendMessage(tab.id, {
16+
action: 'downloadDetermined',
17+
downloadItem,
18+
});
19+
});
20+
});
21+
});
22+
23+
chrome.downloads.onChanged.addListener((downloadItem) => {
24+
chrome.tabs.query({}, (tabs) => {
25+
tabs.forEach((tab) => {
26+
chrome.tabs.sendMessage(tab.id, {
27+
action: 'downloadChanged',
28+
downloadItem,
29+
});
30+
});
31+
});
32+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const DOWNLOAD_KEY = "_DOWNLOADS_";
2+
const DOWNLOADS_PERSISTED = localStorage.getItem(DOWNLOAD_KEY) ? JSON.parse(localStorage.getItem(DOWNLOAD_KEY) ?? "[]") : [];
3+
const DOWNLOADS = DOWNLOADS_PERSISTED.reduce((acc, download) => {
4+
acc[download.id] = download;
5+
return acc;
6+
}, {});
7+
8+
chrome.runtime.onMessage.addListener((message) => {
9+
const {action, downloadItem} = message;
10+
if (action === 'downloadStarted') {
11+
DOWNLOADS[downloadItem.id] = {
12+
id: downloadItem.id,
13+
fileName: '',
14+
fileUrl: '',
15+
state: downloadItem.state,
16+
startTime: new Date(downloadItem.startTime).getTime(),
17+
};
18+
updatePageVariable();
19+
return;
20+
}
21+
22+
if (action === 'downloadDetermined') {
23+
const download = DOWNLOADS[downloadItem.id];
24+
download.fileName = downloadItem.filename;
25+
download.state = downloadItem.state;
26+
updatePageVariable();
27+
return;
28+
}
29+
30+
if (action === 'downloadChanged') {
31+
const download = DOWNLOADS[downloadItem.id];
32+
const filePath = downloadItem.filename?.current;
33+
const state = downloadItem.state?.current;
34+
if (filePath) {
35+
download.filePath = filePath;
36+
}
37+
if (state) {
38+
download.state = state;
39+
}
40+
updatePageVariable();
41+
}
42+
});
43+
44+
function updatePageVariable() {
45+
const downloads = Object.values(DOWNLOADS);
46+
downloads.sort((a, b) => b.startTime - a.startTime);
47+
localStorage.setItem('_DOWNLOADS_', JSON.stringify(downloads));
48+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "Download Monitor Extension",
4+
"version": "1.0",
5+
"permissions": [
6+
"downloads",
7+
"downloads.shelf",
8+
"tabs",
9+
"activeTab",
10+
"storage"
11+
],
12+
"background": {
13+
"service_worker": "background.js"
14+
},
15+
"content_scripts": [
16+
{
17+
"matches": ["<all_urls>"],
18+
"js": ["content.js"]
19+
}
20+
]
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const crxFilePath = path.join(__dirname, './extension.crx');
5+
6+
let CACHED_CRX_BASE64 = null;
7+
8+
function getCrxBase64() {
9+
if (CACHED_CRX_BASE64) {
10+
return CACHED_CRX_BASE64;
11+
}
12+
13+
const crxData = fs.readFileSync(crxFilePath);
14+
CACHED_CRX_BASE64 = crxData.toString('base64');
15+
16+
return CACHED_CRX_BASE64;
17+
}
18+
19+
module.exports = {
20+
getCrxBase64,
21+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBhPtoAnu/e1jg
3+
OTn1twETyvuuri4i82iFL+Pxm5xFxb2f9y1apFgtPCV4ZQnReso4f60tojmWrk1b
4+
3vZaXK3em9GgMaYOm76f+t6qY+w0IaGhg/NTj69pZ43pzxTkTXmc0CDqrz+0osWb
5+
bh3qZdugcnxDmX3RSYQBMf/oWAjtkETbCnWaxX0m0WQGrXRbjVC+yQVEl7xNSFMW
6+
PCS5T254Tv41i+q4LaL14D+PA1TXcwHTmfZyrX/Iz+deKZnlAK4kKFl9X+7c+y++
7+
pnUYpwfhfkXwONwO/hy6c3c8f3PR/vegMsCuPCCzBHZrTXPdlFnKin6CAvzjB1r6
8+
rznBVGOBAgMBAAECggEBAJnEvCvSRVhKf71zW222Y6HBmakceEaHWRbzjdFOj6cV
9+
T+7K7nvmuLYA49k9l8afJg4szYPEMrRbfdaxXNlCaVnIQJJkwQk8kgT2x3Vm/qoR
10+
yyfW/EL6mixL/4S4amZadXa4Hl+8rwcui4xMvHKjSxe7wKfKUCI7oyt7+lc5lKaG
11+
p01BBYTHQaFUmuLRMxWlTZr6qZM1btXXd0A9qQQOUsRZ8N017LdB6+VYCWY1t36q
12+
Ey8FrbZKVXFvwt9yd8MxdaOHmSpsIGLlnNpJykneP9f17tT8UskH0GQgjgZ9ba3I
13+
8l1RysCP802pHOwaMfNtMYJ11i85tBaIz7AB/neZqJECgYEA+cLj7C2wYDvCiTd3
14+
Nmp51sdM8UP4pYLF8F73XUNEyG6VRBXmzJqIOSKrlGqgEgfU/gjpWtdtef2NKMNY
15+
fdz0k5Oyv7De+es24KI+7rlhP6Ptv7YM2urpm8C42i4iC7XBGtgxyaqnqPaQm283
16+
TM4Tqdx40Y9TpMM2JYWuqd4aVV0CgYEAxlpza0b/2q6mM79T8eCdjjCRW1G8NMl8
17+
PXvQrpPo0pLRrTptpqMDg7JRcCnxNifHGna8PsrzHkWVjsfwYkwbp2NANG+eoXI9
18+
wTnqtIUsFTRCeqkKl3SPyi4EAih3bluEZC+WdvDiy3eWXiCfHxz/EVZGw9suoeCu
19+
g/7oMjNi4HUCgYAkiyx4IRM+cV/8Xb42mwuqrkyGvJBD/0dg7TQ6VB5bSTrT1HSJ
20+
mU63NWhvdc5n9PdoF/u0y/J7t+qQfUyUVeD/OswbmhB19sF3yqV0nnEpM54Uv9lP
21+
qrF1lZQ2cCuRFQ3lFJ7sR+jyIulzpKkttrVP1C9lUhhF8j4Y7V9qAVJPDQKBgEg3
22+
pHA5kGvZTK/oiDK3egXMDxA1iRWbCj4Ed20ocws/41FzxXp3PY9UfCwfSTBTeT1c
23+
X5tpHu01noc2qoHPff4Kt9Sfkxzq0Csq4BZLqkoqFc48/5s3GCcfa9wxSZKHhYNI
24+
hDrX52r3Jmss62JTl1aDmA41HhxYBpIOXBHy/ZwFAoGAEJnzQ1NzHH52MArcwrm5
25+
Ku636x3PflPxP7aAYU3GbGJCLLsvS2V0ABxjk/VcHrHCbeIZxZ0YQX7BIAXOkO1a
26+
Dh2fubgnpqDNFIyeFt9ZHJ0Zg5KC3NXK75ErbVSC3WPht3T3va7hITvGVbMkXCRA
27+
Bducv2SfztUPms8SlB4GVIQ=
28+
-----END PRIVATE KEY-----

packages/download-collector-crx/test/empty.spec.ts

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2015",
4+
"module": "commonjs",
5+
"allowJs": false,
6+
"moduleResolution": "node",
7+
"noFallthroughCasesInSwitch": true,
8+
"allowSyntheticDefaultImports": true,
9+
"preserveConstEnums": true,
10+
"noEmitOnError": true,
11+
"declaration": false,
12+
"outDir": "dist",
13+
"sourceMap": false,
14+
"lib": ["es5", "es2015", "es2016", "es2017", "dom"],
15+
"typeRoots": ["./node_modules/@types", "../../node_modules/@types"],
16+
"noUnusedLocals": true,
17+
"strictNullChecks": true,
18+
"removeComments": false
19+
},
20+
"include": ["*.ts", "src/**/*.ts", "src/**/*.json"],
21+
"exclude": ["./node_modules", "../../node_modules"]
22+
}

packages/plugin-selenium-driver/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@nullcc/code-coverage-client": "1.4.2",
1414
"@testring/child-process": "0.6.11",
1515
"@testring/devtool-extension": "0.6.11",
16+
"@testring/dwnld-collector-crx": "0.6.11",
1617
"@testring/logger": "0.6.11",
1718
"@testring/plugin-api": "0.6.11",
1819
"@testring/types": "0.6.11",

packages/plugin-selenium-driver/src/plugin/index.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as deepmerge from 'deepmerge';
88

99
import {spawn} from '@testring/child-process';
1010
import {loggerClient} from '@testring/logger';
11+
import {getCrxBase64} from '@testring/dwnld-collector-crx';
1112
import {absoluteExtensionPath} from '@testring/devtool-extension';
1213
import {CDPCoverageCollector} from '@nullcc/code-coverage-client';
1314

@@ -49,7 +50,7 @@ const DEFAULT_CONFIG: SeleniumPluginConfig = {
4950
args: [] as string[],
5051
},
5152
},
52-
cdpCoverage: false
53+
cdpCoverage: false,
5354
};
5455

5556
function delay(timeout) {
@@ -185,6 +186,23 @@ export class SeleniumPlugin implements IBrowserProxyPlugin {
185186
mergedConfig.hostname = mergedConfig.host;
186187
}
187188

189+
const googleChromeOptions = mergedConfig.capabilities?.['goog:chromeOptions'];
190+
if (
191+
googleChromeOptions &&
192+
(
193+
googleChromeOptions.args?.includes('--headless') ||
194+
googleChromeOptions.args?.includes('headless')
195+
)
196+
) {
197+
const extensions = mergedConfig.capabilities?.['goog:chromeOptions'].extensions;
198+
const dowldMonitorCrx = getCrxBase64();
199+
if (extensions) {
200+
extensions.push(dowldMonitorCrx);
201+
} else {
202+
googleChromeOptions.extensions = [dowldMonitorCrx];
203+
}
204+
}
205+
188206
return mergedConfig;
189207
}
190208

@@ -331,7 +349,10 @@ export class SeleniumPlugin implements IBrowserProxyPlugin {
331349
}
332350
}
333351

334-
private async createClient(applicant: string, config?: Partial<WebdriverIO.Config>): Promise<void> {
352+
private async createClient(
353+
applicant: string,
354+
config?: Partial<WebdriverIO.Config>,
355+
): Promise<void> {
335356
await this.waitForReadyState;
336357
const clientData = this.browserClients.get(applicant);
337358

@@ -350,7 +371,11 @@ export class SeleniumPlugin implements IBrowserProxyPlugin {
350371
);
351372
}
352373

353-
const _config: any = deepmerge.all([{}, this.config, config as any || {}]);
374+
const _config: any = deepmerge.all([
375+
{},
376+
this.config,
377+
(config as any) || {},
378+
]);
354379
const client = await remote(_config);
355380

356381
let sessionId: string;
@@ -424,10 +449,8 @@ export class SeleniumPlugin implements IBrowserProxyPlugin {
424449
client.addCommand(
425450
'deleteSessionId',
426451
function (sessionId) {
427-
const {
428-
w3cCaps,
429-
jsonwpCaps,
430-
} = this.options.requestedCapabilities;
452+
const {w3cCaps, jsonwpCaps} =
453+
this.options.requestedCapabilities;
431454

432455
const sessionDeleteRequest = new WebDriverRequest(
433456
'DELETE',
@@ -1189,7 +1212,7 @@ export class SeleniumPlugin implements IBrowserProxyPlugin {
11891212
},
11901213
},
11911214
},
1192-
} as any)
1215+
} as any);
11931216
}
11941217
}
11951218

0 commit comments

Comments
 (0)