diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index 414389e992..fcdec0d3f9 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -333,6 +333,31 @@ jobs: name: firefox-stable-mac-test-results path: test-results + edge_stable_mac: + name: "Edge Stable (Mac)" + runs-on: macos-10.15 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 12 + - run: npm ci + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + - run: npm run build + - run: node lib/cli/cli install msedge + - run: npm run ctest + env: + PWTEST_CHANNEL: msedge + - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json + if: always() + - uses: actions/upload-artifact@v1 + if: ${{ always() }} + with: + name: msedge-stable-mac-test-results + path: test-results + + edge_stable_win: name: "Edge Stable (Win)" runs-on: windows-latest @@ -345,7 +370,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: node lib/cli/cli install ffmpeg + - run: node lib/cli/cli install msedge - run: npm run ctest shell: bash env: diff --git a/bin/reinstall_chrome_beta_linux.sh b/bin/reinstall_chrome_beta_linux.sh index bf43df3648..8a0a6876d7 100755 --- a/bin/reinstall_chrome_beta_linux.sh +++ b/bin/reinstall_chrome_beta_linux.sh @@ -13,3 +13,4 @@ wget https://dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb sudo apt-get install -y ./google-chrome-beta_current_amd64.deb rm -rf ./google-chrome-beta_current_amd64.deb cd - +google-chrome-beta --version diff --git a/bin/reinstall_chrome_beta_mac.sh b/bin/reinstall_chrome_beta_mac.sh index f924050299..910e57b051 100755 --- a/bin/reinstall_chrome_beta_mac.sh +++ b/bin/reinstall_chrome_beta_mac.sh @@ -8,3 +8,6 @@ curl -o ./googlechromebeta.dmg -k https://dl.google.com/chrome/mac/beta/googlech hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechromebeta.dmg ./googlechromebeta.dmg cp -rf "/Volumes/googlechromebeta.dmg/Google Chrome Beta.app" /Applications hdiutil detach /Volumes/googlechromebeta.dmg +rm -rf /tmp/googlechromebeta.dmg + +/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta --version diff --git a/bin/reinstall_chrome_beta_win.ps1 b/bin/reinstall_chrome_beta_win.ps1 index e3aaafbcc3..61e4578ae9 100644 --- a/bin/reinstall_chrome_beta_win.ps1 +++ b/bin/reinstall_chrome_beta_win.ps1 @@ -4,17 +4,22 @@ if ([Environment]::Is64BitProcess) { $url = 'https://dl.google.com/tag/s/dl/chrome/install/beta/googlechromebetastandaloneenterprise64.msi' } -$app = Get-WmiObject -Class Win32_Product | Where-Object { - $_.Name -match "Google Chrome Beta" -} -if ($app) { - $app.Uninstall() -} - +Write-Host "Downloading Google Chrome Beta" $wc = New-Object net.webclient $msiInstaller = "$env:temp\google-chrome-beta.msi" -Remove-Item $msiInstaller $wc.Downloadfile($url, $msiInstaller) +Write-Host "Installing Google Chrome Beta" $arguments = "/i `"$msiInstaller`" /quiet" Start-Process msiexec.exe -ArgumentList $arguments -Wait +Remove-Item $msiInstaller + +$suffix = "\\Google\\Chrome Beta\\Application\\chrome.exe" +if (Test-Path "${env:ProgramFiles(x86)}$suffix") { + (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo +} elseif (Test-Path "${env:ProgramFiles}$suffix") { + (Get-Item "${env:ProgramFiles}$suffix").VersionInfo +} else { + write-host "ERROR: failed to install Google Chrome Beta" + exit 1 +} diff --git a/bin/reinstall_chrome_stable_linux.sh b/bin/reinstall_chrome_stable_linux.sh index b2c076d381..8c16080920 100755 --- a/bin/reinstall_chrome_stable_linux.sh +++ b/bin/reinstall_chrome_stable_linux.sh @@ -14,3 +14,4 @@ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo apt-get install -y ./google-chrome-stable_current_amd64.deb rm -rf ./google-chrome-stable_current_amd64.deb cd - +google-chrome --version diff --git a/bin/reinstall_chrome_stable_mac.sh b/bin/reinstall_chrome_stable_mac.sh index 6e48424f83..486040bbf8 100755 --- a/bin/reinstall_chrome_stable_mac.sh +++ b/bin/reinstall_chrome_stable_mac.sh @@ -4,7 +4,9 @@ set -x rm -rf "/Applications/Google Chrome.app" cd /tmp -curl -o ./googlechrome.dmg -k https://dl.google.com/chrome/mac/beta/googlechrome.dmg +curl -o ./googlechrome.dmg -k https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechrome.dmg ./googlechrome.dmg cp -rf "/Volumes/googlechrome.dmg/Google Chrome.app" /Applications hdiutil detach /Volumes/googlechrome.dmg +rm -rf /tmp/googlechrome.dmg +/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version diff --git a/bin/reinstall_chrome_stable_win.ps1 b/bin/reinstall_chrome_stable_win.ps1 index 9002b7cfa9..87d949369b 100644 --- a/bin/reinstall_chrome_stable_win.ps1 +++ b/bin/reinstall_chrome_stable_win.ps1 @@ -1,20 +1,26 @@ -$url = 'https://dl.google.com/tag/s/dl/chrome/install/beta/googlechromestandaloneenterprise.msi'; +$url = 'https://dl.google.com/tag/s/dl/chrome/install/googlechromestandaloneenterprise.msi'; if ([Environment]::Is64BitProcess) { - $url = 'https://dl.google.com/tag/s/dl/chrome/install/beta/googlechromestandaloneenterprise64.msi' -} - -$app = Get-WmiObject -Class Win32_Product | Where-Object { - $_.Name -eq "Google Chrome" -} -if ($app) { - $app.Uninstall() + $url = 'https://dl.google.com/tag/s/dl/chrome/install/googlechromestandaloneenterprise64.msi' } $wc = New-Object net.webclient $msiInstaller = "$env:temp\google-chrome.msi" -Remove-Item $msiInstaller +Write-Host "Downloading Google Chrome" $wc.Downloadfile($url, $msiInstaller) +Write-Host "Installing Google Chrome" $arguments = "/i `"$msiInstaller`" /quiet" Start-Process msiexec.exe -ArgumentList $arguments -Wait +Remove-Item $msiInstaller + + +$suffix = "\\Google\\Chrome\\Application\\chrome.exe" +if (Test-Path "${env:ProgramFiles(x86)}$suffix") { + (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo +} elseif (Test-Path "${env:ProgramFiles}$suffix") { + (Get-Item "${env:ProgramFiles}$suffix").VersionInfo +} else { + write-host "ERROR: failed to install Google Chrome" + exit 1 +} diff --git a/bin/reinstall_msedge_stable_mac.sh b/bin/reinstall_msedge_stable_mac.sh new file mode 100755 index 0000000000..9e0f53ecfe --- /dev/null +++ b/bin/reinstall_msedge_stable_mac.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e +set -x + +cd /tmp +curl -o ./msedge_stable.pkg -k "$1" +# Note: there's no way to uninstall previously installed MSEdge. +# However, running PKG again seems to update installation. +sudo installer -pkg /tmp/msedge_stable.pkg -target / +rm -rf /tmp/msedge_stable.pkg +/Applications/Microsoft\ Edge.app/Contents/MacOS/Microsoft\ Edge --version diff --git a/bin/reinstall_msedge_stable_win.ps1 b/bin/reinstall_msedge_stable_win.ps1 new file mode 100644 index 0000000000..85e50d8bea --- /dev/null +++ b/bin/reinstall_msedge_stable_win.ps1 @@ -0,0 +1,21 @@ +$url = $args[0] + +Write-Host "Downloading Microsoft Edge" +$wc = New-Object net.webclient +$msiInstaller = "$env:temp\microsoft-edge-stable.msi" +$wc.Downloadfile($url, $msiInstaller) + +Write-Host "Installing Microsoft Edge" +$arguments = "/i `"$msiInstaller`" /quiet" +Start-Process msiexec.exe -ArgumentList $arguments -Wait +Remove-Item $msiInstaller + +$suffix = "\\Microsoft\\Edge\\Application\\msedge.exe" +if (Test-Path "${env:ProgramFiles(x86)}$suffix") { + (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo +} elseif (Test-Path "${env:ProgramFiles}$suffix") { + (Get-Item "${env:ProgramFiles}$suffix").VersionInfo +} else { + write-host "ERROR: failed to install Microsoft Edge" + exit 1 +} \ No newline at end of file diff --git a/src/cli/cli.ts b/src/cli/cli.ts index c1dc5ff734..36f81bfbee 100755 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -39,10 +39,34 @@ import * as utils from '../utils/utils'; const SCRIPTS_DIRECTORY = path.join(__dirname, '..', '..', 'bin'); -type BrowserChannel = 'chrome-beta'|'chrome'; -const allBrowserChannels: Set = new Set(['chrome-beta', 'chrome']); + +type BrowserChannel = 'chrome-beta'|'chrome'|'msedge'; +const allBrowserChannels: Set = new Set(['chrome-beta', 'chrome', 'msedge']); const packageJSON = require('../../package.json'); +const ChannelName = { + 'chrome-beta': 'Google Chrome Beta', + 'chrome': 'Google Chrome', + 'msedge': 'Microsoft Edge', +}; + +const InstallationScriptName = { + 'chrome-beta': { + 'linux': 'reinstall_chrome_beta_linux.sh', + 'darwin': 'reinstall_chrome_beta_mac.sh', + 'win32': 'reinstall_chrome_beta_win.ps1', + }, + 'chrome': { + 'linux': 'reinstall_chrome_stable_linux.sh', + 'darwin': 'reinstall_chrome_stable_mac.sh', + 'win32': 'reinstall_chrome_stable_win.ps1', + }, + 'msedge': { + 'darwin': 'reinstall_msedge_stable_mac.sh', + 'win32': 'reinstall_msedge_stable_win.ps1', + }, +}; + program .version('Version ' + packageJSON.version) .name(process.env.PW_CLI_NAME || 'npx playwright'); @@ -107,47 +131,46 @@ program console.log(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${[...allBrowserNames, ...allBrowserChannels].map(name => `'${name}'`).join(', ')}`); process.exit(1); } - if (browserNames.has('chromium') || browserChannels.has('chrome-beta') || browserChannels.has('chrome')) + if (browserNames.has('chromium') || browserChannels.has('chrome-beta') || browserChannels.has('chrome') || browserChannels.has('msedge')) browserNames.add('ffmpeg'); if (browserNames.size) await installBrowsers([...browserNames]); - for (const browserChannel of browserChannels) { - if (browserChannel === 'chrome-beta' || browserChannel === 'chrome') - await installChromeChannel(browserChannel); - else - throw new Error(`ERROR: no installation instructions for '${browserChannel}' channel.`); - } + for (const browserChannel of browserChannels) + await installBrowserChannel(browserChannel); } catch (e) { console.log(`Failed to install browsers\n${e}`); process.exit(1); } }); -async function installChromeChannel(channel: string) { - const platform: string = os.platform(); - const shell: (string|undefined) = { - 'linux': 'bash', - 'darwin': 'bash', - 'win32': 'powershell.exe', - }[platform]; - const scriptName: (string|undefined) = ({ - 'chrome-beta': { - 'linux': 'reinstall_chrome_beta_linux.sh', - 'darwin': 'reinstall_chrome_beta_mac.sh', - 'win32': 'reinstall_chrome_beta_win.ps1', - }, - 'chrome': { - 'linux': 'reinstall_chrome_stable_linux.sh', - 'darwin': 'reinstall_chrome_stable_mac.sh', - 'win32': 'reinstall_chrome_stable_win.ps1', - }, - }[channel] as any)[platform]; - if (!shell || !scriptName) - throw new Error(`Cannot install chrome-beta on ${platform}`); +async function installBrowserChannel(channel: BrowserChannel) { + const platform = os.platform(); + const scriptName: (string|undefined) = (InstallationScriptName[channel] as any)[platform]; + if (!scriptName) + throw new Error(`Cannot install ${ChannelName[channel]} on ${platform}`); - const {code} = await utils.spawnAsync(shell, [path.join(SCRIPTS_DIRECTORY, scriptName)], { cwd: SCRIPTS_DIRECTORY, stdio: 'inherit' }); + const scriptArgs = []; + if (channel === 'msedge') { + const products = JSON.parse(await utils.fetchData('https://edgeupdates.microsoft.com/api/products')); + const stable = products.find((product: any) => product.Product === 'Stable'); + if (platform === 'win32') { + const arch = os.arch() === 'x64' ? 'x64' : 'x86'; + const release = stable.Releases.find((release: any) => release.Platform === 'Windows' && release.Architecture === arch); + const artifact = release.Artifacts.find((artifact: any) => artifact.ArtifactName === 'msi'); + scriptArgs.push(artifact.Location /* url */); + } else if (platform === 'darwin') { + const release = stable.Releases.find((release: any) => release.Platform === 'MacOS' && release.Architecture === 'universal'); + const artifact = release.Artifacts.find((artifact: any) => artifact.ArtifactName === 'pkg'); + scriptArgs.push(artifact.Location /* url */); + } else { + throw new Error(`Cannot install ${ChannelName[channel]} on ${platform}`); + } + } + + const shell = scriptName.endsWith('.ps1') ? 'powershell.exe' : 'bash'; + const {code} = await utils.spawnAsync(shell, [path.join(SCRIPTS_DIRECTORY, scriptName), ...scriptArgs], { cwd: SCRIPTS_DIRECTORY, stdio: 'inherit' }); if (code !== 0) - throw new Error('Failed to install chrome-beta'); + throw new Error(`Failed to install ${ChannelName[channel]}`); } program diff --git a/src/install/browserFetcher.ts b/src/install/browserFetcher.ts index 494b10d231..e28186ec80 100644 --- a/src/install/browserFetcher.ts +++ b/src/install/browserFetcher.ts @@ -20,24 +20,10 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; import ProgressBar from 'progress'; -import { getProxyForUrl } from 'proxy-from-env'; -import * as URL from 'url'; import { BrowserName, Registry, hostPlatform } from '../utils/registry'; +import { downloadFile, existsAsync } from '../utils/utils'; import { debugLogger } from '../utils/debugLogger'; -// `https-proxy-agent` v5 is written in TypeScript and exposes generated types. -// However, as of June 2020, its types are generated with tsconfig that enables -// `esModuleInterop` option. -// -// As a result, we can't depend on the package unless we enable the option -// for our codebase. Instead of doing this, we abuse "require" to import module -// without types. -const ProxyAgent = require('https-proxy-agent'); - -const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); - -export type OnProgressCallback = (downloadedBytes: number, totalBytes: number) => void; - export async function downloadBrowserWithProgressBar(registry: Registry, browserName: BrowserName): Promise { const browserDirectory = registry.browserDirectory(browserName); const progressBarName = `${browserName} v${registry.revision(browserName)}`; @@ -71,7 +57,7 @@ export async function downloadBrowserWithProgressBar(registry: Registry, browser try { for (let attempt = 1, N = 3; attempt <= N; ++attempt) { debugLogger.log('install', `downloading ${progressBarName} - attempt #${attempt}`); - const {error} = await downloadFile(url, zipPath, progress); + const {error} = await downloadFile(url, zipPath, {progressCallback: progress, log: debugLogger.log.bind(debugLogger, 'install')}); if (!error) { debugLogger.log('install', `SUCCESS downloading ${progressBarName}`); break; @@ -111,77 +97,6 @@ function toMegabytes(bytes: number) { return `${Math.round(mb * 10) / 10} Mb`; } -function downloadFile(url: string, destinationPath: string, progressCallback: OnProgressCallback | undefined): Promise<{error: any}> { - debugLogger.log('install', `running download:`); - debugLogger.log('install', `-- from url: ${url}`); - debugLogger.log('install', `-- to location: ${destinationPath}`); - let fulfill: ({error}: {error: any}) => void = ({error}) => {}; - let downloadedBytes = 0; - let totalBytes = 0; - - const promise: Promise<{error: any}> = new Promise(x => { fulfill = x; }); - - const request = httpRequest(url, 'GET', response => { - if (response.statusCode !== 200) { - const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`); - // consume response data to free up memory - response.resume(); - fulfill({error}); - return; - } - const file = fs.createWriteStream(destinationPath); - file.on('finish', () => fulfill({error: null})); - file.on('error', error => fulfill({error})); - response.pipe(file); - totalBytes = parseInt(response.headers['content-length'], 10); - debugLogger.log('install', `-- total bytes: ${totalBytes}`); - if (progressCallback) - response.on('data', onData); - }); - request.on('error', (error: any) => fulfill({error})); - return promise; - - function onData(chunk: string) { - downloadedBytes += chunk.length; - progressCallback!(downloadedBytes, totalBytes); - } -} - -function httpRequest(url: string, method: string, response: (r: any) => void) { - let options: any = URL.parse(url); - options.method = method; - - const proxyURL = getProxyForUrl(url); - if (proxyURL) { - if (url.startsWith('http:')) { - const proxy = URL.parse(proxyURL); - options = { - path: options.href, - host: proxy.hostname, - port: proxy.port, - }; - } else { - const parsedProxyURL: any = URL.parse(proxyURL); - parsedProxyURL.secureProxy = parsedProxyURL.protocol === 'https:'; - - options.agent = new ProxyAgent(parsedProxyURL); - options.rejectUnauthorized = false; - } - } - - const requestCallback = (res: any) => { - if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) - httpRequest(res.headers.location, method, response); - else - response(res); - }; - const request = options.protocol === 'https:' ? - require('https').request(options, requestCallback) : - require('http').request(options, requestCallback); - request.end(); - return request; -} - export function logPolitely(toBeLogged: string) { const logLevel = process.env.npm_config_loglevel; const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel || '') > -1; diff --git a/src/server/browserType.ts b/src/server/browserType.ts index d876f1a1a2..627a1dde8e 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -27,12 +27,11 @@ import { Progress, ProgressController } from './progress'; import * as types from './types'; import { DEFAULT_TIMEOUT, TimeoutSettings } from '../utils/timeoutSettings'; import { validateHostRequirements } from './validateDependencies'; -import { debugMode } from '../utils/utils'; +import { debugMode, existsAsync } from '../utils/utils'; import { helper } from './helper'; import { RecentLogsCollector } from '../utils/debugLogger'; import { CallMetadata, SdkObject } from './instrumentation'; -const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-'); export abstract class BrowserType extends SdkObject { diff --git a/src/server/supplements/recorder/recorderApp.ts b/src/server/supplements/recorder/recorderApp.ts index 14b559c8dd..c47ba55057 100644 --- a/src/server/supplements/recorder/recorderApp.ts +++ b/src/server/supplements/recorder/recorderApp.ts @@ -23,9 +23,7 @@ import { EventEmitter } from 'events'; import { internalCallMetadata } from '../../instrumentation'; import type { CallLog, EventData, Mode, Source } from './recorderTypes'; import { BrowserContext } from '../../browserContext'; -import { isUnderTest } from '../../../utils/utils'; - -const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); +import { existsAsync, isUnderTest } from '../../../utils/utils'; declare global { interface Window { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b94802920f..b5547bbe45 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -20,6 +20,113 @@ import removeFolder from 'rimraf'; import * as crypto from 'crypto'; import os from 'os'; import { spawn } from 'child_process'; +import { getProxyForUrl } from 'proxy-from-env'; +import * as URL from 'url'; + +// `https-proxy-agent` v5 is written in TypeScript and exposes generated types. +// However, as of June 2020, its types are generated with tsconfig that enables +// `esModuleInterop` option. +// +// As a result, we can't depend on the package unless we enable the option +// for our codebase. Instead of doing this, we abuse "require" to import module +// without types. +const ProxyAgent = require('https-proxy-agent'); + +export const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); + +function httpRequest(url: string, method: string, response: (r: any) => void) { + let options: any = URL.parse(url); + options.method = method; + + const proxyURL = getProxyForUrl(url); + if (proxyURL) { + if (url.startsWith('http:')) { + const proxy = URL.parse(proxyURL); + options = { + path: options.href, + host: proxy.hostname, + port: proxy.port, + }; + } else { + const parsedProxyURL: any = URL.parse(proxyURL); + parsedProxyURL.secureProxy = parsedProxyURL.protocol === 'https:'; + + options.agent = new ProxyAgent(parsedProxyURL); + options.rejectUnauthorized = false; + } + } + + const requestCallback = (res: any) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) + httpRequest(res.headers.location, method, response); + else + response(res); + }; + const request = options.protocol === 'https:' ? + require('https').request(options, requestCallback) : + require('http').request(options, requestCallback); + request.end(); + return request; +} + +export function fetchData(url: string): Promise { + return new Promise((resolve, reject) => { + httpRequest(url, 'GET', function(response){ + if (response.statusCode !== 200) { + reject(new Error(`fetch failed: server returned code ${response.statusCode}. URL: ${url}`)); + return; + } + let body = ''; + response.on('data', (chunk: string) => body += chunk); + response.on('error', (error: any) => reject(error)); + response.on('end', () => resolve(body)); + }).on('error', (error: any) => reject(error)); + }); +} + +type OnProgressCallback = (downloadedBytes: number, totalBytes: number) => void; +type DownloadFileLogger = (message: string) => void; + +export function downloadFile(url: string, destinationPath: string, options : {progressCallback?: OnProgressCallback, log?: DownloadFileLogger} = {}): Promise<{error: any}> { + const { + progressCallback, + log = () => {}, + } = options; + log(`running download:`); + log(`-- from url: ${url}`); + log(`-- to location: ${destinationPath}`); + let fulfill: ({error}: {error: any}) => void = ({error}) => {}; + let downloadedBytes = 0; + let totalBytes = 0; + + const promise: Promise<{error: any}> = new Promise(x => { fulfill = x; }); + + const request = httpRequest(url, 'GET', response => { + log(`-- response status code: ${response.statusCode}`); + if (response.statusCode !== 200) { + const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`); + // consume response data to free up memory + response.resume(); + fulfill({error}); + return; + } + const file = fs.createWriteStream(destinationPath); + file.on('finish', () => fulfill({error: null})); + file.on('error', error => fulfill({error})); + response.pipe(file); + totalBytes = parseInt(response.headers['content-length'], 10); + log(`-- total bytes: ${totalBytes}`); + if (progressCallback) + response.on('data', onData); + }); + request.on('error', (error: any) => fulfill({error})); + return promise; + + function onData(chunk: string) { + downloadedBytes += chunk.length; + progressCallback!(downloadedBytes, totalBytes); + } +} export function spawnAsync(cmd: string, args: string[], options: any): Promise<{stdout: string, stderr: string, code: number, error?: Error}> { const process = spawn(cmd, args, options); diff --git a/tests/headful.spec.ts b/tests/headful.spec.ts index c83f30ddc8..d4cd5c0636 100644 --- a/tests/headful.spec.ts +++ b/tests/headful.spec.ts @@ -148,6 +148,7 @@ it('Page.bringToFront should work', async ({browserType, browserOptions}) => { }); it('focused input should produce the same screenshot', async ({browserType, browserOptions, browserName, platform, channel}, testInfo) => { + it.fail(channel === 'msedge' && platform === 'darwin', 'focus ring is black on MSEdge'); it.fail(browserName === 'firefox' && platform === 'darwin', 'headless has thinner outline'); it.fail(browserName === 'firefox' && platform === 'linux', 'headless has no outline'); it.skip(browserName === 'webkit' && platform === 'linux', 'gtk vs wpe');