From 66ef39269fc795dcb58a0835acb250906a8add98 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 9 Nov 2020 18:17:45 -0800 Subject: [PATCH] feat(scopes): make page a scope --- .github/workflows/tests.yml | 37 ++++++++++++++++++++++ .gitignore | 1 + src/server/clank/android.ts | 27 ++++++---------- utils/avd_install.sh | 27 ++++++++++++++++ utils/avd_recreate.sh | 9 ++++++ utils/avd_start.sh | 8 +++++ utils/avd_test.js | 63 +++++++++++++++++++++++++++++++++++++ 7 files changed, 154 insertions(+), 18 deletions(-) create mode 100755 utils/avd_install.sh create mode 100755 utils/avd_recreate.sh create mode 100755 utils/avd_start.sh create mode 100644 utils/avd_test.js diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 205a29fdeb5882..e7464662ba9820 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -230,3 +230,40 @@ jobs: with: name: video-${{ matrix.browser }}-linux-test-results path: test-results + + android: + runs-on: macos-11.0 + env: + ANDROID_SDK_ROOT: .android-sdk + ANDROID_AVD_HOME: .android-sdk/avd + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 10.15 + - uses: microsoft/playwright-github-action@v1 + - run: npm ci + - run: npm run build + - run: mkdir .android-sdk && mkdir .android-sdk/avd + - name: Fetch SDK + working-directory: .android-sdk + run: mkdir cmdline-tools && cd cmdline-tools && curl https://dl.google.com/android/repository/commandlinetools-mac-6858069_latest.zip -o commandlinetools-mac-6858069_latest.zip + - name: Unzip SDK + working-directory: .android-sdk/cmdline-tools + run: unzip commandlinetools-mac-6858069_latest.zip && mv cmdline-tools latest + - name: Install emulator + working-directory: .android-sdk/cmdline-tools/latest/bin + run: yes | ./sdkmanager platform-tools emulator + - name: Install system image + working-directory: .android-sdk/cmdline-tools/latest/bin + run: ./sdkmanager platform-tools emulator "system-images;android-30;google_apis;x86" + - name: Install platform SDK + working-directory: .android-sdk/cmdline-tools/latest/bin + run: ./sdkmanager platform-tools emulator "platforms;android-30" + - name: Start ADB + working-directory: .android-sdk/platform-tools + run: ./sdkmanager platform-tools emulator "platforms;android-30" + - name: Create AVD instance + run: adb devices + - name: run test + run: node utils/avd_test.js diff --git a/.gitignore b/.gitignore index 9a7f8f91f7db31..4506eee5d6b3af 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ lib/ jest-report.json drivers/ /docs/api.json +.android-sdk/ diff --git a/src/server/clank/android.ts b/src/server/clank/android.ts index aa71c811f9c3d2..a8cadc6d7d4526 100644 --- a/src/server/clank/android.ts +++ b/src/server/clank/android.ts @@ -18,7 +18,7 @@ import * as debug from 'debug'; import { EventEmitter } from 'events'; import * as stream from 'stream'; import * as ws from 'ws'; -import { makeWaitForNextTask } from '../../utils/utils'; +import { createGuid, makeWaitForNextTask } from '../../utils/utils'; export interface Backend { devices(): Promise; @@ -69,20 +69,13 @@ export class AndroidDevice { async launchBrowser(packageName: string): Promise { debug('pw:android')('Force-stopping', packageName); await this.backend.runCommand(`shell:am force-stop ${packageName}`); - const hasDefaultSocket = !!(await this.backend.runCommand(`shell:cat /proc/net/unix | grep chrome_devtools_remote$`)); - debug('pw:android')('Starting', packageName); + + const socketName = createGuid(); + const commandLine = `_ --disable-fre --no-default-browser-check --no-first-run --remote-debugging-socket-name=${socketName}`; + debug('pw:android')('Starting', packageName, commandLine); + await this.backend.runCommand(`shell:echo "${commandLine}" > /data/local/tmp/chrome-command-line`); await this.backend.runCommand(`shell:am start -n ${packageName}/com.google.android.apps.chrome.Main about:blank`); - let pid = 0; - debug('pw:android')('Polling pid for', packageName); - while (!pid) { - const ps = (await this.backend.runCommand(`shell:ps -A | grep ${packageName}`)).split('\n'); - const proc = ps.find(line => line.endsWith(packageName)); - if (proc) - pid = +proc.replace(/\s+/g, ' ').split(' ')[1]; - await new Promise(f => setTimeout(f, 100)); - } - debug('pw:android')('PID=' + pid); - const socketName = hasDefaultSocket ? `chrome_devtools_remote_${pid}` : 'chrome_devtools_remote'; + debug('pw:android')('Polling for socket', socketName); while (true) { const net = await this.backend.runCommand(`shell:cat /proc/net/unix | grep ${socketName}$`); @@ -91,7 +84,7 @@ export class AndroidDevice { await new Promise(f => setTimeout(f, 100)); } debug('pw:android')('Got the socket, connecting'); - const browser = new AndroidBrowser(this, packageName, socketName, pid); + const browser = new AndroidBrowser(this, packageName, socketName); await browser._open(); return browser; } @@ -104,7 +97,6 @@ export class AndroidDevice { export class AndroidBrowser extends EventEmitter { readonly device: AndroidDevice; readonly socketName: string; - readonly pid: number; private _socket: SocketBackend | undefined; private _receiver: stream.Writable; private _waitForNextTask = makeWaitForNextTask(); @@ -112,12 +104,11 @@ export class AndroidBrowser extends EventEmitter { onclose?: () => void; private _packageName: string; - constructor(device: AndroidDevice, packageName: string, socketName: string, pid: number) { + constructor(device: AndroidDevice, packageName: string, socketName: string) { super(); this._packageName = packageName; this.device = device; this.socketName = socketName; - this.pid = pid; this._receiver = new (ws as any).Receiver() as stream.Writable; this._receiver.on('message', message => { this._waitForNextTask(() => { diff --git a/utils/avd_install.sh b/utils/avd_install.sh new file mode 100755 index 00000000000000..5f1310c0d62d27 --- /dev/null +++ b/utils/avd_install.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +SDKDIR=$PWD/.android-sdk +export ANDROID_SDK_ROOT=${SDKDIR} +export ANDROID_HOME=${SDKDIR} +export ANDROID_AVD_HOME=${SDKDIR}/avd + +mkdir ${SDKDIR} +mkdir ${SDKDIR}/cmdline-tools + +echo Downloading Android SDK... +cd ${SDKDIR}/cmdline-tools +curl https://dl.google.com/android/repository/commandlinetools-mac-6858069_latest.zip -o commandlinetools-mac-6858069_latest.zip +unzip commandlinetools-mac-6858069_latest.zip +mv cmdline-tools latest + +echo Installing emulator... +yes | ${SDKDIR}/cmdline-tools/latest/bin/sdkmanager platform-tools emulator + +echo Installing system image... +${SDKDIR}/cmdline-tools/latest/bin/sdkmanager "system-images;android-30;google_apis;x86" + +echo Installing platform SDK... +${SDKDIR}/cmdline-tools/latest/bin/sdkmanager "platforms;android-30" + +echo Starting ADB... +${SDKDIR}/platform-tools/adb devices diff --git a/utils/avd_recreate.sh b/utils/avd_recreate.sh new file mode 100755 index 00000000000000..fefd8f6b17ffcf --- /dev/null +++ b/utils/avd_recreate.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +SDKDIR=$PWD/.android-sdk +export ANDROID_SDK_ROOT=${SDKDIR} +export ANDROID_HOME=${SDKDIR} +export ANDROID_AVD_HOME=${SDKDIR}/avd + +${SDKDIR}/cmdline-tools/latest/bin/avdmanager delete avd --name android30 +echo -ne '\n' | ${SDKDIR}/cmdline-tools/latest/bin/avdmanager create avd --name android30 --device pixel_4_xl --package "system-images;android-30;google_apis;x86" diff --git a/utils/avd_start.sh b/utils/avd_start.sh new file mode 100755 index 00000000000000..1874a57ac2defc --- /dev/null +++ b/utils/avd_start.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +SDKDIR=$PWD/.android-sdk +export ANDROID_SDK_ROOT=${SDKDIR} +export ANDROID_HOME=${SDKDIR} +export ANDROID_AVD_HOME=${SDKDIR}/avd + +${SDKDIR}/emulator/emulator -avd android30 diff --git a/utils/avd_test.js b/utils/avd_test.js new file mode 100644 index 00000000000000..f9aa7335b5ba17 --- /dev/null +++ b/utils/avd_test.js @@ -0,0 +1,63 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { _clank } = require('..'); +const assert = require('assert'); +const childProcess = require('child_process'); +const path = require('path'); +const readline = require('readline'); + +(async () => { + setTimeout(() => { + console.error('Timed out starting emulator'); + process.exit(1); + }, 60000); + const proc = childProcess.spawn(path.join(process.cwd(), '.android-sdk/emulator/emulator'), ['-no-window', '-avd', 'android30', '-verbose'], { + env: { + ...process.env, + ANDROID_SDK_ROOT: path.join(process.cwd(), '.android-sdk'), + ANDROID_HOME: path.join(process.cwd(), '.android-sdk'), + } + }); + proc.stdout.on('data', data => console.log(data.toString())); + proc.stderr.on('data', data => console.log(data.toString())); + await waitForLine(proc, /boot completed/); + + const context = await _clank.launchPersistentContext(''); + const [page] = context.pages(); + await page.goto('data:text/html,Hello world'); + assert(await page.title() === 'Hello world'); + await context.close(); + process.exit(0); +})(); + +async function waitForLine(proc, regex) { + return new Promise((resolve, reject) => { + const rl = readline.createInterface({ input: proc.stdout }); + const failError = new Error('Process failed to launch!'); + rl.on('line', onLine); + rl.on('close', reject.bind(null, failError)); + proc.on('exit', reject.bind(null, failError)); + proc.on('error', reject.bind(null, failError)); + + function onLine(line) { + const match = line.match(regex); + if (!match) + return; + resolve(match); + } + }); +}