playwright project
This commit is contained in:
parent
187d46c64c
commit
e31b2bd477
1
PlaywrightProject/PlaywrightTests/GlobalUsings.cs
Normal file
1
PlaywrightProject/PlaywrightTests/GlobalUsings.cs
Normal file
@ -0,0 +1 @@
|
|||||||
|
global using NUnit.Framework;
|
21
PlaywrightProject/PlaywrightTests/PlaywrightTests.csproj
Normal file
21
PlaywrightProject/PlaywrightTests/PlaywrightTests.csproj
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||||
|
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.37.1" />
|
||||||
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
|
||||||
|
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
15
PlaywrightProject/PlaywrightTests/UnitTest1.cs
Normal file
15
PlaywrightProject/PlaywrightTests/UnitTest1.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace PlaywrightTests;
|
||||||
|
|
||||||
|
public class Tests
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test1()
|
||||||
|
{
|
||||||
|
Assert.Pass();
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
if not defined PLAYWRIGHT_NODEJS_PATH set PLAYWRIGHT_NODEJS_PATH=%~dp0node.exe
|
||||||
|
"%PLAYWRIGHT_NODEJS_PATH%" "%~dp0..\..\package\lib\cli\cli.js" %*
|
@ -0,0 +1,3 @@
|
|||||||
|
# playwright-core
|
||||||
|
|
||||||
|
This package contains the no-browser flavor of [Playwright](http://github.com/microsoft/playwright).
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1,2 @@
|
|||||||
|
See building instructions at [`/browser_patches/winldd/README.md`](../../../browser_patches/winldd/README.md)
|
||||||
|
|
@ -0,0 +1,5 @@
|
|||||||
|
$osInfo = Get-WmiObject -Class Win32_OperatingSystem
|
||||||
|
# check if running on Windows Server
|
||||||
|
if ($osInfo.ProductType -eq 3) {
|
||||||
|
Install-WindowsFeature Server-Media-Foundation
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if [[ $(arch) == "aarch64" ]]; then
|
||||||
|
echo "ERROR: not supported on Linux Arm64"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "/etc/os-release" ]]; then
|
||||||
|
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ID=$(bash -c 'source /etc/os-release && echo $ID')
|
||||||
|
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
|
||||||
|
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. make sure to remove old beta if any.
|
||||||
|
if dpkg --get-selections | grep -q "^google-chrome-beta[[:space:]]*install$" >/dev/null; then
|
||||||
|
apt-get remove -y google-chrome-beta
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Update apt lists (needed to install curl and chrome dependencies)
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
# 3. Install curl to download chrome
|
||||||
|
if ! command -v curl >/dev/null; then
|
||||||
|
apt-get install -y curl
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. download chrome beta from dl.google.com and install it.
|
||||||
|
cd /tmp
|
||||||
|
curl -O https://dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb
|
||||||
|
apt-get install -y ./google-chrome-beta_current_amd64.deb
|
||||||
|
rm -rf ./google-chrome-beta_current_amd64.deb
|
||||||
|
cd -
|
||||||
|
google-chrome-beta --version
|
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
rm -rf "/Applications/Google Chrome Beta.app"
|
||||||
|
cd /tmp
|
||||||
|
curl -o ./googlechromebeta.dmg -k https://dl.google.com/chrome/mac/universal/beta/googlechromebeta.dmg
|
||||||
|
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
|
@ -0,0 +1,25 @@
|
|||||||
|
$url = 'https://dl.google.com/tag/s/dl/chrome/install/beta/googlechromebetastandaloneenterprise.msi';
|
||||||
|
|
||||||
|
if ([Environment]::Is64BitProcess) {
|
||||||
|
$url = 'https://dl.google.com/tag/s/dl/chrome/install/beta/googlechromebetastandaloneenterprise64.msi'
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Downloading Google Chrome Beta"
|
||||||
|
$wc = New-Object net.webclient
|
||||||
|
$msiInstaller = "$env:temp\google-chrome-beta.msi"
|
||||||
|
$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
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if [[ $(arch) == "aarch64" ]]; then
|
||||||
|
echo "ERROR: not supported on Linux Arm64"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "/etc/os-release" ]]; then
|
||||||
|
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ID=$(bash -c 'source /etc/os-release && echo $ID')
|
||||||
|
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
|
||||||
|
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. make sure to remove old stable if any.
|
||||||
|
if dpkg --get-selections | grep -q "^google-chrome[[:space:]]*install$" >/dev/null; then
|
||||||
|
apt-get remove -y google-chrome
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Update apt lists (needed to install curl and chrome dependencies)
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
# 3. Install curl to download chrome
|
||||||
|
if ! command -v curl >/dev/null; then
|
||||||
|
apt-get install -y curl
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. download chrome stable from dl.google.com and install it.
|
||||||
|
cd /tmp
|
||||||
|
curl -O https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||||
|
apt-get install -y ./google-chrome-stable_current_amd64.deb
|
||||||
|
rm -rf ./google-chrome-stable_current_amd64.deb
|
||||||
|
cd -
|
||||||
|
google-chrome --version
|
@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
rm -rf "/Applications/Google Chrome.app"
|
||||||
|
cd /tmp
|
||||||
|
curl -o ./googlechrome.dmg -k https://dl.google.com/chrome/mac/universal/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
|
@ -0,0 +1,26 @@
|
|||||||
|
$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/googlechromestandaloneenterprise64.msi'
|
||||||
|
}
|
||||||
|
|
||||||
|
$wc = New-Object net.webclient
|
||||||
|
$msiInstaller = "$env:temp\google-chrome.msi"
|
||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if [[ $(arch) == "aarch64" ]]; then
|
||||||
|
echo "ERROR: not supported on Linux Arm64"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "/etc/os-release" ]]; then
|
||||||
|
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ID=$(bash -c 'source /etc/os-release && echo $ID')
|
||||||
|
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
|
||||||
|
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. make sure to remove old beta if any.
|
||||||
|
if dpkg --get-selections | grep -q "^microsoft-edge-beta[[:space:]]*install$" >/dev/null; then
|
||||||
|
apt-get remove -y microsoft-edge-beta
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Install curl to download Microsoft gpg key
|
||||||
|
if ! command -v curl >/dev/null; then
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y curl
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
|
||||||
|
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
|
||||||
|
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
|
||||||
|
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list'
|
||||||
|
rm /tmp/microsoft.gpg
|
||||||
|
apt-get update && apt-get install -y microsoft-edge-beta
|
||||||
|
|
||||||
|
microsoft-edge-beta --version
|
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cd /tmp
|
||||||
|
curl -o ./msedge_beta.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_beta.pkg -target /
|
||||||
|
rm -rf /tmp/msedge_beta.pkg
|
||||||
|
/Applications/Microsoft\ Edge\ Beta.app/Contents/MacOS/Microsoft\ Edge\ Beta --version
|
@ -0,0 +1,21 @@
|
|||||||
|
$url = $args[0]
|
||||||
|
|
||||||
|
Write-Host "Downloading Microsoft Edge Beta"
|
||||||
|
$wc = New-Object net.webclient
|
||||||
|
$msiInstaller = "$env:temp\microsoft-edge-beta.msi"
|
||||||
|
$wc.Downloadfile($url, $msiInstaller)
|
||||||
|
|
||||||
|
Write-Host "Installing Microsoft Edge Beta"
|
||||||
|
$arguments = "/i `"$msiInstaller`" /quiet"
|
||||||
|
Start-Process msiexec.exe -ArgumentList $arguments -Wait
|
||||||
|
Remove-Item $msiInstaller
|
||||||
|
|
||||||
|
$suffix = "\\Microsoft\\Edge Beta\\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
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if [[ $(arch) == "aarch64" ]]; then
|
||||||
|
echo "ERROR: not supported on Linux Arm64"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "/etc/os-release" ]]; then
|
||||||
|
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ID=$(bash -c 'source /etc/os-release && echo $ID')
|
||||||
|
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
|
||||||
|
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. make sure to remove old dev if any.
|
||||||
|
if dpkg --get-selections | grep -q "^microsoft-edge-dev[[:space:]]*install$" >/dev/null; then
|
||||||
|
apt-get remove -y microsoft-edge-dev
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Install curl to download Microsoft gpg key
|
||||||
|
if ! command -v curl >/dev/null; then
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y curl
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
|
||||||
|
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
|
||||||
|
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
|
||||||
|
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list'
|
||||||
|
rm /tmp/microsoft.gpg
|
||||||
|
apt-get update && apt-get install -y microsoft-edge-dev
|
||||||
|
|
||||||
|
microsoft-edge-dev --version
|
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cd /tmp
|
||||||
|
curl -o ./msedge_dev.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_dev.pkg -target /
|
||||||
|
rm -rf /tmp/msedge_dev.pkg
|
||||||
|
/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev --version
|
@ -0,0 +1,21 @@
|
|||||||
|
$url = $args[0]
|
||||||
|
|
||||||
|
Write-Host "Downloading Microsoft Edge Dev"
|
||||||
|
$wc = New-Object net.webclient
|
||||||
|
$msiInstaller = "$env:temp\microsoft-edge-dev.msi"
|
||||||
|
$wc.Downloadfile($url, $msiInstaller)
|
||||||
|
|
||||||
|
Write-Host "Installing Microsoft Edge Dev"
|
||||||
|
$arguments = "/i `"$msiInstaller`" /quiet"
|
||||||
|
Start-Process msiexec.exe -ArgumentList $arguments -Wait
|
||||||
|
Remove-Item $msiInstaller
|
||||||
|
|
||||||
|
$suffix = "\\Microsoft\\Edge Dev\\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
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if [[ $(arch) == "aarch64" ]]; then
|
||||||
|
echo "ERROR: not supported on Linux Arm64"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "/etc/os-release" ]]; then
|
||||||
|
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ID=$(bash -c 'source /etc/os-release && echo $ID')
|
||||||
|
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
|
||||||
|
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. make sure to remove old stable if any.
|
||||||
|
if dpkg --get-selections | grep -q "^microsoft-edge-stable[[:space:]]*install$" >/dev/null; then
|
||||||
|
apt-get remove -y microsoft-edge-stable
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Install curl to download Microsoft gpg key
|
||||||
|
if ! command -v curl >/dev/null; then
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y curl
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
|
||||||
|
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
|
||||||
|
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
|
||||||
|
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-stable.list'
|
||||||
|
rm /tmp/microsoft.gpg
|
||||||
|
apt-get update && apt-get install -y microsoft-edge-stable
|
||||||
|
|
||||||
|
microsoft-edge-stable --version
|
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env 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
|
@ -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
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"comment": "Do not edit this file, use utils/roll_browser.js",
|
||||||
|
"browsers": [
|
||||||
|
{
|
||||||
|
"name": "chromium",
|
||||||
|
"revision": "1076",
|
||||||
|
"installByDefault": true,
|
||||||
|
"browserVersion": "116.0.5845.82"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chromium-with-symbols",
|
||||||
|
"revision": "1076",
|
||||||
|
"installByDefault": false,
|
||||||
|
"browserVersion": "116.0.5845.82"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chromium-tip-of-tree",
|
||||||
|
"revision": "1136",
|
||||||
|
"installByDefault": false,
|
||||||
|
"browserVersion": "117.0.5913.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "firefox",
|
||||||
|
"revision": "1422",
|
||||||
|
"installByDefault": true,
|
||||||
|
"browserVersion": "115.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "firefox-asan",
|
||||||
|
"revision": "1422",
|
||||||
|
"installByDefault": false,
|
||||||
|
"browserVersion": "115.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "firefox-beta",
|
||||||
|
"revision": "1422",
|
||||||
|
"installByDefault": false,
|
||||||
|
"browserVersion": "116.0b2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "webkit",
|
||||||
|
"revision": "1883",
|
||||||
|
"installByDefault": true,
|
||||||
|
"revisionOverrides": {
|
||||||
|
"mac10.14": "1446",
|
||||||
|
"mac10.15": "1616",
|
||||||
|
"mac11": "1816",
|
||||||
|
"mac11-arm64": "1816",
|
||||||
|
"ubuntu18.04": "1728"
|
||||||
|
},
|
||||||
|
"browserVersion": "17.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ffmpeg",
|
||||||
|
"revision": "1009",
|
||||||
|
"installByDefault": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "android",
|
||||||
|
"revision": "1000",
|
||||||
|
"installByDefault": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
module.exports = require('playwright-core/lib/cli/cli');
|
17
PlaywrightProject/PlaywrightTests/bin/Debug/net7.0/.playwright/package/index.d.ts
vendored
Normal file
17
PlaywrightProject/PlaywrightTests/bin/Debug/net7.0/.playwright/package/index.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './types/types';
|
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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 minimumMajorNodeVersion = 14;
|
||||||
|
const currentNodeVersion = process.versions.node;
|
||||||
|
const semver = currentNodeVersion.split('.');
|
||||||
|
const [major] = [+semver[0]];
|
||||||
|
|
||||||
|
if (major < minimumMajorNodeVersion) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
'You are running Node.js ' +
|
||||||
|
currentNodeVersion +
|
||||||
|
'.\n' +
|
||||||
|
`Playwright requires Node.js ${minimumMajorNodeVersion} or higher. \n` +
|
||||||
|
'Please update your version of Node.js.'
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = require('./lib/inprocess');
|
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import playwright from './index.js';
|
||||||
|
|
||||||
|
export const chromium = playwright.chromium;
|
||||||
|
export const firefox = playwright.firefox;
|
||||||
|
export const webkit = playwright.webkit;
|
||||||
|
export const selectors = playwright.selectors;
|
||||||
|
export const devices = playwright.devices;
|
||||||
|
export const errors = playwright.errors;
|
||||||
|
export const request = playwright.request;
|
||||||
|
export const _electron = playwright._electron;
|
||||||
|
export const _android = playwright._android;
|
||||||
|
export default playwright;
|
@ -0,0 +1,69 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.AndroidServerLauncherImpl = void 0;
|
||||||
|
var _utilsBundle = require("./utilsBundle");
|
||||||
|
var _utils = require("./utils");
|
||||||
|
var _playwright = require("./server/playwright");
|
||||||
|
var _playwrightServer = require("./remote/playwrightServer");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class AndroidServerLauncherImpl {
|
||||||
|
async launchServer(options = {}) {
|
||||||
|
const playwright = (0, _playwright.createPlaywright)({
|
||||||
|
sdkLanguage: 'javascript',
|
||||||
|
isServer: true
|
||||||
|
});
|
||||||
|
// 1. Pre-connect to the device
|
||||||
|
let devices = await playwright.android.devices({
|
||||||
|
host: options.adbHost,
|
||||||
|
port: options.adbPort,
|
||||||
|
omitDriverInstall: options.omitDriverInstall
|
||||||
|
});
|
||||||
|
if (devices.length === 0) throw new Error('No devices found');
|
||||||
|
if (options.deviceSerialNumber) {
|
||||||
|
devices = devices.filter(d => d.serial === options.deviceSerialNumber);
|
||||||
|
if (devices.length === 0) throw new Error(`No device with serial number '${options.deviceSerialNumber}' not found`);
|
||||||
|
}
|
||||||
|
if (devices.length > 1) throw new Error(`More than one device found. Please specify deviceSerialNumber`);
|
||||||
|
const device = devices[0];
|
||||||
|
const path = options.wsPath ? options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}` : `/${(0, _utils.createGuid)()}`;
|
||||||
|
|
||||||
|
// 2. Start the server
|
||||||
|
const server = new _playwrightServer.PlaywrightServer({
|
||||||
|
mode: 'launchServer',
|
||||||
|
path,
|
||||||
|
maxConnections: 1,
|
||||||
|
preLaunchedAndroidDevice: device
|
||||||
|
});
|
||||||
|
const wsEndpoint = await server.listen(options.port);
|
||||||
|
|
||||||
|
// 3. Return the BrowserServer interface
|
||||||
|
const browserServer = new _utilsBundle.ws.EventEmitter();
|
||||||
|
browserServer.wsEndpoint = () => wsEndpoint;
|
||||||
|
browserServer.close = () => device.close();
|
||||||
|
browserServer.kill = () => device.close();
|
||||||
|
device.on('close', () => {
|
||||||
|
server.close();
|
||||||
|
browserServer.emit('close');
|
||||||
|
});
|
||||||
|
return browserServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.AndroidServerLauncherImpl = AndroidServerLauncherImpl;
|
@ -0,0 +1,91 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.BrowserServerLauncherImpl = void 0;
|
||||||
|
var _utilsBundle = require("./utilsBundle");
|
||||||
|
var _clientHelper = require("./client/clientHelper");
|
||||||
|
var _utils = require("./utils");
|
||||||
|
var _instrumentation = require("./server/instrumentation");
|
||||||
|
var _playwright = require("./server/playwright");
|
||||||
|
var _playwrightServer = require("./remote/playwrightServer");
|
||||||
|
var _helper = require("./server/helper");
|
||||||
|
var _stackTrace = require("./utils/stackTrace");
|
||||||
|
var _socksProxy = require("./common/socksProxy");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class BrowserServerLauncherImpl {
|
||||||
|
constructor(browserName) {
|
||||||
|
this._browserName = void 0;
|
||||||
|
this._browserName = browserName;
|
||||||
|
}
|
||||||
|
async launchServer(options = {}) {
|
||||||
|
const playwright = (0, _playwright.createPlaywright)({
|
||||||
|
sdkLanguage: 'javascript',
|
||||||
|
isServer: true
|
||||||
|
});
|
||||||
|
// TODO: enable socks proxy once ipv6 is supported.
|
||||||
|
const socksProxy = false ? new _socksProxy.SocksProxy() : undefined;
|
||||||
|
playwright.options.socksProxyPort = await (socksProxy === null || socksProxy === void 0 ? void 0 : socksProxy.listen(0));
|
||||||
|
|
||||||
|
// 1. Pre-launch the browser
|
||||||
|
const metadata = (0, _instrumentation.serverSideCallMetadata)();
|
||||||
|
const browser = await playwright[this._browserName].launch(metadata, {
|
||||||
|
...options,
|
||||||
|
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
||||||
|
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||||
|
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined
|
||||||
|
}, toProtocolLogger(options.logger)).catch(e => {
|
||||||
|
const log = _helper.helper.formatBrowserLogs(metadata.log);
|
||||||
|
(0, _stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
const path = options.wsPath ? options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}` : `/${(0, _utils.createGuid)()}`;
|
||||||
|
|
||||||
|
// 2. Start the server
|
||||||
|
const server = new _playwrightServer.PlaywrightServer({
|
||||||
|
mode: 'launchServer',
|
||||||
|
path,
|
||||||
|
maxConnections: Infinity,
|
||||||
|
preLaunchedBrowser: browser,
|
||||||
|
preLaunchedSocksProxy: socksProxy
|
||||||
|
});
|
||||||
|
const wsEndpoint = await server.listen(options.port);
|
||||||
|
|
||||||
|
// 3. Return the BrowserServer interface
|
||||||
|
const browserServer = new _utilsBundle.ws.EventEmitter();
|
||||||
|
browserServer.process = () => browser.options.browserProcess.process;
|
||||||
|
browserServer.wsEndpoint = () => wsEndpoint;
|
||||||
|
browserServer.close = () => browser.options.browserProcess.close();
|
||||||
|
browserServer.kill = () => browser.options.browserProcess.kill();
|
||||||
|
browserServer._disconnectForTest = () => server.close();
|
||||||
|
browserServer._userDataDirForTest = browser._userDataDirForTest;
|
||||||
|
browser.options.browserProcess.onclose = (exitCode, signal) => {
|
||||||
|
socksProxy === null || socksProxy === void 0 ? void 0 : socksProxy.close().catch(() => {});
|
||||||
|
server.close();
|
||||||
|
browserServer.emit('close', exitCode, signal);
|
||||||
|
};
|
||||||
|
return browserServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.BrowserServerLauncherImpl = BrowserServerLauncherImpl;
|
||||||
|
function toProtocolLogger(logger) {
|
||||||
|
return logger ? (direction, message) => {
|
||||||
|
if (logger.isEnabled('protocol', 'verbose')) logger.log('protocol', 'verbose', (direction === 'send' ? 'SEND ► ' : '◀ RECV ') + JSON.stringify(message), [], {});
|
||||||
|
} : undefined;
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _program = _interopRequireDefault(require("./program"));
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
function printPlaywrightTestError(command) {
|
||||||
|
const packages = [];
|
||||||
|
for (const pkg of ['playwright', 'playwright-chromium', 'playwright-firefox', 'playwright-webkit']) {
|
||||||
|
try {
|
||||||
|
require.resolve(pkg);
|
||||||
|
packages.push(pkg);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
if (!packages.length) packages.push('playwright');
|
||||||
|
const packageManager = (0, _utils.getPackageManager)();
|
||||||
|
if (packageManager === 'yarn') {
|
||||||
|
console.error(`Please install @playwright/test package before running "yarn playwright ${command}"`);
|
||||||
|
console.error(` yarn remove ${packages.join(' ')}`);
|
||||||
|
console.error(' yarn add -D @playwright/test');
|
||||||
|
} else if (packageManager === 'pnpm') {
|
||||||
|
console.error(`Please install @playwright/test package before running "pnpm exec playwright ${command}"`);
|
||||||
|
console.error(` pnpm remove ${packages.join(' ')}`);
|
||||||
|
console.error(' pnpm add -D @playwright/test');
|
||||||
|
} else {
|
||||||
|
console.error(`Please install @playwright/test package before running "npx playwright ${command}"`);
|
||||||
|
console.error(` npm uninstall ${packages.join(' ')}`);
|
||||||
|
console.error(' npm install -D @playwright/test');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const command = _program.default.command('test').allowUnknownOption(true);
|
||||||
|
command.description('Run tests with Playwright Test. Available in @playwright/test package.');
|
||||||
|
command.action(async () => {
|
||||||
|
printPlaywrightTestError('test');
|
||||||
|
(0, _utils.gracefullyProcessExitDoNotHang)(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const command = _program.default.command('show-report').allowUnknownOption(true);
|
||||||
|
command.description('Show Playwright Test HTML report. Available in @playwright/test package.');
|
||||||
|
command.action(async () => {
|
||||||
|
printPlaywrightTestError('show-report');
|
||||||
|
(0, _utils.gracefullyProcessExitDoNotHang)(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_program.default.parse(process.argv);
|
@ -0,0 +1,88 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.launchBrowserServer = launchBrowserServer;
|
||||||
|
exports.printApiJson = printApiJson;
|
||||||
|
exports.runDriver = runDriver;
|
||||||
|
exports.runServer = runServer;
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var playwright = _interopRequireWildcard(require("../.."));
|
||||||
|
var _server = require("../server");
|
||||||
|
var _transport = require("../protocol/transport");
|
||||||
|
var _playwrightServer = require("../remote/playwrightServer");
|
||||||
|
var _processLauncher = require("../utils/processLauncher");
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
function printApiJson() {
|
||||||
|
// Note: this file is generated by build-playwright-driver.sh
|
||||||
|
console.log(JSON.stringify(require('../../api.json')));
|
||||||
|
}
|
||||||
|
function runDriver() {
|
||||||
|
const dispatcherConnection = new _server.DispatcherConnection();
|
||||||
|
new _server.RootDispatcher(dispatcherConnection, async (rootScope, {
|
||||||
|
sdkLanguage
|
||||||
|
}) => {
|
||||||
|
const playwright = (0, _server.createPlaywright)({
|
||||||
|
sdkLanguage
|
||||||
|
});
|
||||||
|
return new _server.PlaywrightDispatcher(rootScope, playwright);
|
||||||
|
});
|
||||||
|
const transport = new _transport.PipeTransport(process.stdout, process.stdin);
|
||||||
|
transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message));
|
||||||
|
dispatcherConnection.onmessage = message => transport.send(JSON.stringify(message));
|
||||||
|
transport.onclose = () => {
|
||||||
|
// Drop any messages during shutdown on the floor.
|
||||||
|
dispatcherConnection.onmessage = () => {};
|
||||||
|
(0, _processLauncher.gracefullyProcessExitDoNotHang)(0);
|
||||||
|
};
|
||||||
|
// Ignore the SIGINT signal in the driver process so the parent can gracefully close the connection.
|
||||||
|
// We still will destruct everything (close browsers and exit) when the transport pipe closes.
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
// Keep the process running.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function runServer(options) {
|
||||||
|
const {
|
||||||
|
port,
|
||||||
|
path = '/',
|
||||||
|
maxConnections = Infinity,
|
||||||
|
extension
|
||||||
|
} = options;
|
||||||
|
const server = new _playwrightServer.PlaywrightServer({
|
||||||
|
mode: extension ? 'extension' : 'default',
|
||||||
|
path,
|
||||||
|
maxConnections
|
||||||
|
});
|
||||||
|
const wsEndpoint = await server.listen(port);
|
||||||
|
process.on('exit', () => server.close().catch(console.error));
|
||||||
|
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
||||||
|
process.stdin.on('close', () => (0, _processLauncher.gracefullyProcessExitDoNotHang)(0));
|
||||||
|
}
|
||||||
|
async function launchBrowserServer(browserName, configFile) {
|
||||||
|
let options = {};
|
||||||
|
if (configFile) options = JSON.parse(_fs.default.readFileSync(configFile).toString());
|
||||||
|
const browserType = playwright[browserName];
|
||||||
|
const server = await browserType.launchServer(options);
|
||||||
|
console.log(server.wsEndpoint());
|
||||||
|
}
|
@ -0,0 +1,569 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.default = void 0;
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _os = _interopRequireDefault(require("os"));
|
||||||
|
var _path = _interopRequireDefault(require("path"));
|
||||||
|
var _utilsBundle = require("../utilsBundle");
|
||||||
|
var _driver = require("./driver");
|
||||||
|
var _traceViewer = require("../server/trace/viewer/traceViewer");
|
||||||
|
var playwright = _interopRequireWildcard(require("../.."));
|
||||||
|
var _child_process = require("child_process");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _server = require("../server");
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
const packageJSON = require('../../package.json');
|
||||||
|
_utilsBundle.program.version('Version ' + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME));
|
||||||
|
_utilsBundle.program.command('mark-docker-image [dockerImageNameTemplate]', {
|
||||||
|
hidden: true
|
||||||
|
}).description('mark docker image').allowUnknownOption(true).action(function (dockerImageNameTemplate) {
|
||||||
|
(0, _utils.assert)(dockerImageNameTemplate, 'dockerImageNameTemplate is required');
|
||||||
|
(0, _server.writeDockerVersion)(dockerImageNameTemplate).catch(logErrorAndExit);
|
||||||
|
});
|
||||||
|
commandWithOpenOptions('open [url]', 'open page in browser specified via -b, --browser', []).action(function (url, options) {
|
||||||
|
open(options, url, codegenId()).catch(logErrorAndExit);
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
$ open
|
||||||
|
$ open -b webkit https://example.com`);
|
||||||
|
commandWithOpenOptions('codegen [url]', 'open page and generate code for user actions', [['-o, --output <file name>', 'saves the generated script to a file'], ['--target <language>', `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java`, codegenId()], ['--save-trace <filename>', 'record a trace for the session and save it to a file'], ['--test-id-attribute <attributeName>', 'use the specified attribute to generate data test ID selectors']]).action(function (url, options) {
|
||||||
|
codegen(options, url).catch(logErrorAndExit);
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
$ codegen
|
||||||
|
$ codegen --target=python
|
||||||
|
$ codegen -b webkit https://example.com`);
|
||||||
|
_utilsBundle.program.command('debug <app> [args...]', {
|
||||||
|
hidden: true
|
||||||
|
}).description('run command in debug mode: disable timeout, open inspector').allowUnknownOption(true).action(function (app, options) {
|
||||||
|
(0, _child_process.spawn)(app, options, {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PWDEBUG: '1'
|
||||||
|
},
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
$ debug node test.js
|
||||||
|
$ debug npm run test`);
|
||||||
|
function suggestedBrowsersToInstall() {
|
||||||
|
return _server.registry.executables().filter(e => e.installType !== 'none' && e.type !== 'tool').map(e => e.name).join(', ');
|
||||||
|
}
|
||||||
|
function checkBrowsersToInstall(args) {
|
||||||
|
const faultyArguments = [];
|
||||||
|
const executables = [];
|
||||||
|
for (const arg of args) {
|
||||||
|
const executable = _server.registry.findExecutable(arg);
|
||||||
|
if (!executable || executable.installType === 'none') faultyArguments.push(arg);else executables.push(executable);
|
||||||
|
}
|
||||||
|
if (faultyArguments.length) throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall()}`);
|
||||||
|
return executables;
|
||||||
|
}
|
||||||
|
_utilsBundle.program.command('install [browser...]').description('ensure browsers necessary for this version of Playwright are installed').option('--with-deps', 'install system dependencies for browsers').option('--dry-run', 'do not execute installation, only print information').option('--force', 'force reinstall of stable browser channels').action(async function (args, options) {
|
||||||
|
if ((0, _utils.isLikelyNpxGlobal)()) {
|
||||||
|
console.error((0, _utils.wrapInASCIIBox)([`WARNING: It looks like you are running 'npx playwright install' without first`, `installing your project's dependencies.`, ``, `To avoid unexpected behavior, please install your dependencies first, and`, `then run Playwright's install command:`, ``, ` npm install`, ` npx playwright install`, ``, `If your project does not yet depend on Playwright, first install the`, `applicable npm package (most commonly @playwright/test), and`, `then run Playwright's install command to download the browsers:`, ``, ` npm install @playwright/test`, ` npx playwright install`, ``].join('\n'), 1));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const hasNoArguments = !args.length;
|
||||||
|
const executables = hasNoArguments ? _server.registry.defaultExecutables() : checkBrowsersToInstall(args);
|
||||||
|
if (options.withDeps) await _server.registry.installDeps(executables, !!options.dryRun);
|
||||||
|
if (options.dryRun) {
|
||||||
|
for (const executable of executables) {
|
||||||
|
var _executable$directory, _executable$downloadU;
|
||||||
|
const version = executable.browserVersion ? `version ` + executable.browserVersion : '';
|
||||||
|
console.log(`browser: ${executable.name}${version ? ' ' + version : ''}`);
|
||||||
|
console.log(` Install location: ${(_executable$directory = executable.directory) !== null && _executable$directory !== void 0 ? _executable$directory : '<system>'}`);
|
||||||
|
if ((_executable$downloadU = executable.downloadURLs) !== null && _executable$downloadU !== void 0 && _executable$downloadU.length) {
|
||||||
|
const [url, ...fallbacks] = executable.downloadURLs;
|
||||||
|
console.log(` Download url: ${url}`);
|
||||||
|
for (let i = 0; i < fallbacks.length; ++i) console.log(` Download fallback ${i + 1}: ${fallbacks[i]}`);
|
||||||
|
}
|
||||||
|
console.log(``);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const forceReinstall = hasNoArguments ? false : !!options.force;
|
||||||
|
await _server.registry.install(executables, forceReinstall);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Failed to install browsers\n${e}`);
|
||||||
|
(0, _utils.gracefullyProcessExitDoNotHang)(1);
|
||||||
|
}
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- $ install
|
||||||
|
Install default browsers.
|
||||||
|
|
||||||
|
- $ install chrome firefox
|
||||||
|
Install custom browsers, supports ${suggestedBrowsersToInstall()}.`);
|
||||||
|
_utilsBundle.program.command('uninstall').description('Removes browsers used by this installation of Playwright from the system (chromium, firefox, webkit, ffmpeg). This does not include branded channels.').option('--all', 'Removes all browsers used by any Playwright installation from the system.').action(async options => {
|
||||||
|
await _server.registry.uninstall(!!options.all).then(({
|
||||||
|
numberOfBrowsersLeft
|
||||||
|
}) => {
|
||||||
|
if (!options.all && numberOfBrowsersLeft > 0) {
|
||||||
|
console.log('Successfully uninstalled Playwright browsers for the current Playwright installation.');
|
||||||
|
console.log(`There are still ${numberOfBrowsersLeft} browsers left, used by other Playwright installations.\nTo uninstall Playwright browsers for all installations, re-run with --all flag.`);
|
||||||
|
}
|
||||||
|
}).catch(logErrorAndExit);
|
||||||
|
});
|
||||||
|
_utilsBundle.program.command('install-deps [browser...]').description('install dependencies necessary to run browsers (will ask for sudo permissions)').option('--dry-run', 'Do not execute installation commands, only print them').action(async function (args, options) {
|
||||||
|
try {
|
||||||
|
if (!args.length) await _server.registry.installDeps(_server.registry.defaultExecutables(), !!options.dryRun);else await _server.registry.installDeps(checkBrowsersToInstall(args), !!options.dryRun);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Failed to install browser dependencies\n${e}`);
|
||||||
|
(0, _utils.gracefullyProcessExitDoNotHang)(1);
|
||||||
|
}
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
Examples:
|
||||||
|
- $ install-deps
|
||||||
|
Install dependencies for default browsers.
|
||||||
|
|
||||||
|
- $ install-deps chrome firefox
|
||||||
|
Install dependencies for specific browsers, supports ${suggestedBrowsersToInstall()}.`);
|
||||||
|
const browsers = [{
|
||||||
|
alias: 'cr',
|
||||||
|
name: 'Chromium',
|
||||||
|
type: 'chromium'
|
||||||
|
}, {
|
||||||
|
alias: 'ff',
|
||||||
|
name: 'Firefox',
|
||||||
|
type: 'firefox'
|
||||||
|
}, {
|
||||||
|
alias: 'wk',
|
||||||
|
name: 'WebKit',
|
||||||
|
type: 'webkit'
|
||||||
|
}];
|
||||||
|
for (const {
|
||||||
|
alias,
|
||||||
|
name,
|
||||||
|
type
|
||||||
|
} of browsers) {
|
||||||
|
commandWithOpenOptions(`${alias} [url]`, `open page in ${name}`, []).action(function (url, options) {
|
||||||
|
open({
|
||||||
|
...options,
|
||||||
|
browser: type
|
||||||
|
}, url, options.target).catch(logErrorAndExit);
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
$ ${alias} https://example.com`);
|
||||||
|
}
|
||||||
|
commandWithOpenOptions('screenshot <url> <filename>', 'capture a page screenshot', [['--wait-for-selector <selector>', 'wait for selector before taking a screenshot'], ['--wait-for-timeout <timeout>', 'wait for timeout in milliseconds before taking a screenshot'], ['--full-page', 'whether to take a full page screenshot (entire scrollable area)']]).action(function (url, filename, command) {
|
||||||
|
screenshot(command, command, url, filename).catch(logErrorAndExit);
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
$ screenshot -b webkit https://example.com example.png`);
|
||||||
|
commandWithOpenOptions('pdf <url> <filename>', 'save page as pdf', [['--wait-for-selector <selector>', 'wait for given selector before saving as pdf'], ['--wait-for-timeout <timeout>', 'wait for given timeout in milliseconds before saving as pdf']]).action(function (url, filename, options) {
|
||||||
|
pdf(options, options, url, filename).catch(logErrorAndExit);
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
$ pdf https://example.com example.pdf`);
|
||||||
|
_utilsBundle.program.command('run-driver', {
|
||||||
|
hidden: true
|
||||||
|
}).action(function (options) {
|
||||||
|
(0, _driver.runDriver)();
|
||||||
|
});
|
||||||
|
_utilsBundle.program.command('run-server', {
|
||||||
|
hidden: true
|
||||||
|
}).option('--port <port>', 'Server port').option('--path <path>', 'Endpoint Path', '/').option('--max-clients <maxClients>', 'Maximum clients').option('--mode <mode>', 'Server mode, either "default" or "extension"').action(function (options) {
|
||||||
|
(0, _driver.runServer)({
|
||||||
|
port: options.port ? +options.port : undefined,
|
||||||
|
path: options.path,
|
||||||
|
maxConnections: options.maxClients ? +options.maxClients : Infinity,
|
||||||
|
extension: options.mode === 'extension' || !!process.env.PW_EXTENSION_MODE
|
||||||
|
}).catch(logErrorAndExit);
|
||||||
|
});
|
||||||
|
_utilsBundle.program.command('print-api-json', {
|
||||||
|
hidden: true
|
||||||
|
}).action(function (options) {
|
||||||
|
(0, _driver.printApiJson)();
|
||||||
|
});
|
||||||
|
_utilsBundle.program.command('launch-server', {
|
||||||
|
hidden: true
|
||||||
|
}).requiredOption('--browser <browserName>', 'Browser name, one of "chromium", "firefox" or "webkit"').option('--config <path-to-config-file>', 'JSON file with launchServer options').action(function (options) {
|
||||||
|
(0, _driver.launchBrowserServer)(options.browser, options.config);
|
||||||
|
});
|
||||||
|
_utilsBundle.program.command('show-trace [trace...]').option('-b, --browser <browserType>', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium').option('-h, --host <host>', 'Host to serve trace on; specifying this option opens trace in a browser tab').option('-p, --port <port>', 'Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab').option('--stdin', 'Accept trace URLs over stdin to update the viewer').description('show trace viewer').action(function (traces, options) {
|
||||||
|
if (options.browser === 'cr') options.browser = 'chromium';
|
||||||
|
if (options.browser === 'ff') options.browser = 'firefox';
|
||||||
|
if (options.browser === 'wk') options.browser = 'webkit';
|
||||||
|
const openOptions = {
|
||||||
|
headless: false,
|
||||||
|
host: options.host,
|
||||||
|
port: +options.port,
|
||||||
|
isServer: !!options.stdin
|
||||||
|
};
|
||||||
|
if (options.port !== undefined || options.host !== undefined) {
|
||||||
|
(0, _traceViewer.openTraceInBrowser)(traces, openOptions).catch(logErrorAndExit);
|
||||||
|
} else {
|
||||||
|
(0, _traceViewer.openTraceViewerApp)(traces, options.browser, openOptions).then(page => {
|
||||||
|
page.on('close', () => (0, _utils.gracefullyProcessExitDoNotHang)(0));
|
||||||
|
}).catch(logErrorAndExit);
|
||||||
|
}
|
||||||
|
}).addHelpText('afterAll', `
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
$ show-trace https://example.com/trace.zip`);
|
||||||
|
async function launchContext(options, headless, executablePath) {
|
||||||
|
validateOptions(options);
|
||||||
|
const browserType = lookupBrowserType(options);
|
||||||
|
const launchOptions = {
|
||||||
|
headless,
|
||||||
|
executablePath
|
||||||
|
};
|
||||||
|
if (options.channel) launchOptions.channel = options.channel;
|
||||||
|
launchOptions.handleSIGINT = false;
|
||||||
|
const contextOptions =
|
||||||
|
// Copy the device descriptor since we have to compare and modify the options.
|
||||||
|
options.device ? {
|
||||||
|
...playwright.devices[options.device]
|
||||||
|
} : {};
|
||||||
|
|
||||||
|
// In headful mode, use host device scale factor for things to look nice.
|
||||||
|
// In headless, keep things the way it works in Playwright by default.
|
||||||
|
// Assume high-dpi on MacOS. TODO: this is not perfect.
|
||||||
|
if (!headless) contextOptions.deviceScaleFactor = _os.default.platform() === 'darwin' ? 2 : 1;
|
||||||
|
|
||||||
|
// Work around the WebKit GTK scrolling issue.
|
||||||
|
if (browserType.name() === 'webkit' && process.platform === 'linux') {
|
||||||
|
delete contextOptions.hasTouch;
|
||||||
|
delete contextOptions.isMobile;
|
||||||
|
}
|
||||||
|
if (contextOptions.isMobile && browserType.name() === 'firefox') contextOptions.isMobile = undefined;
|
||||||
|
if (options.blockServiceWorkers) contextOptions.serviceWorkers = 'block';
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
|
||||||
|
if (options.proxyServer) {
|
||||||
|
launchOptions.proxy = {
|
||||||
|
server: options.proxyServer
|
||||||
|
};
|
||||||
|
if (options.proxyBypass) launchOptions.proxy.bypass = options.proxyBypass;
|
||||||
|
}
|
||||||
|
const browser = await browserType.launch(launchOptions);
|
||||||
|
if (process.env.PWTEST_CLI_IS_UNDER_TEST) {
|
||||||
|
process._didSetSourcesForTest = text => {
|
||||||
|
process.stdout.write('\n-------------8<-------------\n');
|
||||||
|
process.stdout.write(text);
|
||||||
|
process.stdout.write('\n-------------8<-------------\n');
|
||||||
|
const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
|
||||||
|
if (autoExitCondition && text.includes(autoExitCondition)) Promise.all(context.pages().map(async p => p.close()));
|
||||||
|
};
|
||||||
|
// Make sure we exit abnormally when browser crashes.
|
||||||
|
const logs = [];
|
||||||
|
require('playwright-core/lib/utilsBundle').debug.log = (...args) => {
|
||||||
|
const line = require('util').format(...args) + '\n';
|
||||||
|
logs.push(line);
|
||||||
|
process.stderr.write(line);
|
||||||
|
};
|
||||||
|
browser.on('disconnected', () => {
|
||||||
|
const hasCrashLine = logs.some(line => line.includes('process did exit:') && !line.includes('process did exit: exitCode=0, signal=null'));
|
||||||
|
if (hasCrashLine) {
|
||||||
|
process.stderr.write('Detected browser crash.\n');
|
||||||
|
(0, _utils.gracefullyProcessExitDoNotHang)(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Viewport size
|
||||||
|
if (options.viewportSize) {
|
||||||
|
try {
|
||||||
|
const [width, height] = options.viewportSize.split(',').map(n => parseInt(n, 10));
|
||||||
|
contextOptions.viewport = {
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Invalid viewport size format: use "width, height", for example --viewport-size=800,600');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Geolocation
|
||||||
|
|
||||||
|
if (options.geolocation) {
|
||||||
|
try {
|
||||||
|
const [latitude, longitude] = options.geolocation.split(',').map(n => parseFloat(n.trim()));
|
||||||
|
contextOptions.geolocation = {
|
||||||
|
latitude,
|
||||||
|
longitude
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"');
|
||||||
|
}
|
||||||
|
contextOptions.permissions = ['geolocation'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// User agent
|
||||||
|
|
||||||
|
if (options.userAgent) contextOptions.userAgent = options.userAgent;
|
||||||
|
|
||||||
|
// Lang
|
||||||
|
|
||||||
|
if (options.lang) contextOptions.locale = options.lang;
|
||||||
|
|
||||||
|
// Color scheme
|
||||||
|
|
||||||
|
if (options.colorScheme) contextOptions.colorScheme = options.colorScheme;
|
||||||
|
|
||||||
|
// Timezone
|
||||||
|
|
||||||
|
if (options.timezone) contextOptions.timezoneId = options.timezone;
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
|
||||||
|
if (options.loadStorage) contextOptions.storageState = options.loadStorage;
|
||||||
|
if (options.ignoreHttpsErrors) contextOptions.ignoreHTTPSErrors = true;
|
||||||
|
|
||||||
|
// HAR
|
||||||
|
|
||||||
|
if (options.saveHar) {
|
||||||
|
contextOptions.recordHar = {
|
||||||
|
path: _path.default.resolve(process.cwd(), options.saveHar),
|
||||||
|
mode: 'minimal'
|
||||||
|
};
|
||||||
|
if (options.saveHarGlob) contextOptions.recordHar.urlFilter = options.saveHarGlob;
|
||||||
|
contextOptions.serviceWorkers = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close app when the last window closes.
|
||||||
|
|
||||||
|
const context = await browser.newContext(contextOptions);
|
||||||
|
let closingBrowser = false;
|
||||||
|
async function closeBrowser() {
|
||||||
|
// We can come here multiple times. For example, saving storage creates
|
||||||
|
// a temporary page and we call closeBrowser again when that page closes.
|
||||||
|
if (closingBrowser) return;
|
||||||
|
closingBrowser = true;
|
||||||
|
if (options.saveTrace) await context.tracing.stop({
|
||||||
|
path: options.saveTrace
|
||||||
|
});
|
||||||
|
if (options.saveStorage) await context.storageState({
|
||||||
|
path: options.saveStorage
|
||||||
|
}).catch(e => null);
|
||||||
|
if (options.saveHar) await context.close();
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
context.on('page', page => {
|
||||||
|
page.on('dialog', () => {}); // Prevent dialogs from being automatically dismissed.
|
||||||
|
page.on('close', () => {
|
||||||
|
const hasPage = browser.contexts().some(context => context.pages().length > 0);
|
||||||
|
if (hasPage) return;
|
||||||
|
// Avoid the error when the last page is closed because the browser has been closed.
|
||||||
|
closeBrowser().catch(e => null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
process.on('SIGINT', async () => {
|
||||||
|
await closeBrowser();
|
||||||
|
(0, _utils.gracefullyProcessExitDoNotHang)(130);
|
||||||
|
});
|
||||||
|
const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
|
||||||
|
context.setDefaultTimeout(timeout);
|
||||||
|
context.setDefaultNavigationTimeout(timeout);
|
||||||
|
if (options.saveTrace) await context.tracing.start({
|
||||||
|
screenshots: true,
|
||||||
|
snapshots: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Omit options that we add automatically for presentation purpose.
|
||||||
|
delete launchOptions.headless;
|
||||||
|
delete launchOptions.executablePath;
|
||||||
|
delete launchOptions.handleSIGINT;
|
||||||
|
delete contextOptions.deviceScaleFactor;
|
||||||
|
return {
|
||||||
|
browser,
|
||||||
|
browserName: browserType.name(),
|
||||||
|
context,
|
||||||
|
contextOptions,
|
||||||
|
launchOptions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function openPage(context, url) {
|
||||||
|
const page = await context.newPage();
|
||||||
|
if (url) {
|
||||||
|
if (_fs.default.existsSync(url)) url = 'file://' + _path.default.resolve(url);else if (!url.startsWith('http') && !url.startsWith('file://') && !url.startsWith('about:') && !url.startsWith('data:')) url = 'http://' + url;
|
||||||
|
await page.goto(url).catch(error => {
|
||||||
|
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN && error.message.includes('Navigation failed because page was closed')) {
|
||||||
|
// Tests with PWTEST_CLI_AUTO_EXIT_WHEN might close page too fast, resulting
|
||||||
|
// in a stray navigation aborted error. We should ignore it.
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
async function open(options, url, language) {
|
||||||
|
const {
|
||||||
|
context,
|
||||||
|
launchOptions,
|
||||||
|
contextOptions
|
||||||
|
} = await launchContext(options, !!process.env.PWTEST_CLI_HEADLESS, process.env.PWTEST_CLI_EXECUTABLE_PATH);
|
||||||
|
await context._enableRecorder({
|
||||||
|
language,
|
||||||
|
launchOptions,
|
||||||
|
contextOptions,
|
||||||
|
device: options.device,
|
||||||
|
saveStorage: options.saveStorage
|
||||||
|
});
|
||||||
|
await openPage(context, url);
|
||||||
|
}
|
||||||
|
async function codegen(options, url) {
|
||||||
|
const {
|
||||||
|
target: language,
|
||||||
|
output: outputFile,
|
||||||
|
testIdAttribute: testIdAttributeName
|
||||||
|
} = options;
|
||||||
|
const {
|
||||||
|
context,
|
||||||
|
launchOptions,
|
||||||
|
contextOptions
|
||||||
|
} = await launchContext(options, !!process.env.PWTEST_CLI_HEADLESS, process.env.PWTEST_CLI_EXECUTABLE_PATH);
|
||||||
|
await context._enableRecorder({
|
||||||
|
language,
|
||||||
|
launchOptions,
|
||||||
|
contextOptions,
|
||||||
|
device: options.device,
|
||||||
|
saveStorage: options.saveStorage,
|
||||||
|
mode: 'recording',
|
||||||
|
testIdAttributeName,
|
||||||
|
outputFile: outputFile ? _path.default.resolve(outputFile) : undefined,
|
||||||
|
handleSIGINT: false
|
||||||
|
});
|
||||||
|
await openPage(context, url);
|
||||||
|
}
|
||||||
|
async function waitForPage(page, captureOptions) {
|
||||||
|
if (captureOptions.waitForSelector) {
|
||||||
|
console.log(`Waiting for selector ${captureOptions.waitForSelector}...`);
|
||||||
|
await page.waitForSelector(captureOptions.waitForSelector);
|
||||||
|
}
|
||||||
|
if (captureOptions.waitForTimeout) {
|
||||||
|
console.log(`Waiting for timeout ${captureOptions.waitForTimeout}...`);
|
||||||
|
await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function screenshot(options, captureOptions, url, path) {
|
||||||
|
const {
|
||||||
|
context
|
||||||
|
} = await launchContext(options, true);
|
||||||
|
console.log('Navigating to ' + url);
|
||||||
|
const page = await openPage(context, url);
|
||||||
|
await waitForPage(page, captureOptions);
|
||||||
|
console.log('Capturing screenshot into ' + path);
|
||||||
|
await page.screenshot({
|
||||||
|
path,
|
||||||
|
fullPage: !!captureOptions.fullPage
|
||||||
|
});
|
||||||
|
// launchContext takes care of closing the browser.
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
async function pdf(options, captureOptions, url, path) {
|
||||||
|
if (options.browser !== 'chromium') throw new Error('PDF creation is only working with Chromium');
|
||||||
|
const {
|
||||||
|
context
|
||||||
|
} = await launchContext({
|
||||||
|
...options,
|
||||||
|
browser: 'chromium'
|
||||||
|
}, true);
|
||||||
|
console.log('Navigating to ' + url);
|
||||||
|
const page = await openPage(context, url);
|
||||||
|
await waitForPage(page, captureOptions);
|
||||||
|
console.log('Saving as pdf into ' + path);
|
||||||
|
await page.pdf({
|
||||||
|
path
|
||||||
|
});
|
||||||
|
// launchContext takes care of closing the browser.
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
function lookupBrowserType(options) {
|
||||||
|
let name = options.browser;
|
||||||
|
if (options.device) {
|
||||||
|
const device = playwright.devices[options.device];
|
||||||
|
name = device.defaultBrowserType;
|
||||||
|
}
|
||||||
|
let browserType;
|
||||||
|
switch (name) {
|
||||||
|
case 'chromium':
|
||||||
|
browserType = playwright.chromium;
|
||||||
|
break;
|
||||||
|
case 'webkit':
|
||||||
|
browserType = playwright.webkit;
|
||||||
|
break;
|
||||||
|
case 'firefox':
|
||||||
|
browserType = playwright.firefox;
|
||||||
|
break;
|
||||||
|
case 'cr':
|
||||||
|
browserType = playwright.chromium;
|
||||||
|
break;
|
||||||
|
case 'wk':
|
||||||
|
browserType = playwright.webkit;
|
||||||
|
break;
|
||||||
|
case 'ff':
|
||||||
|
browserType = playwright.firefox;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (browserType) return browserType;
|
||||||
|
_utilsBundle.program.help();
|
||||||
|
}
|
||||||
|
function validateOptions(options) {
|
||||||
|
if (options.device && !(options.device in playwright.devices)) {
|
||||||
|
const lines = [`Device descriptor not found: '${options.device}', available devices are:`];
|
||||||
|
for (const name in playwright.devices) lines.push(` "${name}"`);
|
||||||
|
throw new Error(lines.join('\n'));
|
||||||
|
}
|
||||||
|
if (options.colorScheme && !['light', 'dark'].includes(options.colorScheme)) throw new Error('Invalid color scheme, should be one of "light", "dark"');
|
||||||
|
}
|
||||||
|
function logErrorAndExit(e) {
|
||||||
|
if (process.env.PWDEBUGIMPL) console.error(e);else console.error(e.name + ': ' + e.message);
|
||||||
|
(0, _utils.gracefullyProcessExitDoNotHang)(1);
|
||||||
|
}
|
||||||
|
function codegenId() {
|
||||||
|
return process.env.PW_LANG_NAME || 'playwright-test';
|
||||||
|
}
|
||||||
|
function commandWithOpenOptions(command, description, options) {
|
||||||
|
let result = _utilsBundle.program.command(command).description(description);
|
||||||
|
for (const option of options) result = result.option(option[0], ...option.slice(1));
|
||||||
|
return result.option('-b, --browser <browserType>', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium').option('--block-service-workers', 'block service workers').option('--channel <channel>', 'Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc').option('--color-scheme <scheme>', 'emulate preferred color scheme, "light" or "dark"').option('--device <deviceName>', 'emulate device, for example "iPhone 11"').option('--geolocation <coordinates>', 'specify geolocation coordinates, for example "37.819722,-122.478611"').option('--ignore-https-errors', 'ignore https errors').option('--load-storage <filename>', 'load context storage state from the file, previously saved with --save-storage').option('--lang <language>', 'specify language / locale, for example "en-GB"').option('--proxy-server <proxy>', 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option('--proxy-bypass <bypass>', 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option('--save-har <filename>', 'save HAR file with all network activity at the end').option('--save-har-glob <glob pattern>', 'filter entries in the HAR by matching url against this glob pattern').option('--save-storage <filename>', 'save context storage state at the end, for later use with --load-storage').option('--timezone <time zone>', 'time zone to emulate, for example "Europe/Rome"').option('--timeout <timeout>', 'timeout for Playwright actions in milliseconds, no timeout by default').option('--user-agent <ua string>', 'specify user agent string').option('--viewport-size <size>', 'specify browser viewport size in pixels, for example "1280, 720"');
|
||||||
|
}
|
||||||
|
function buildBasePlaywrightCLICommand(cliTargetLang) {
|
||||||
|
switch (cliTargetLang) {
|
||||||
|
case 'python':
|
||||||
|
return `playwright`;
|
||||||
|
case 'java':
|
||||||
|
return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="...options.."`;
|
||||||
|
case 'csharp':
|
||||||
|
return `pwsh bin/Debug/netX/playwright.ps1`;
|
||||||
|
default:
|
||||||
|
return `npx playwright`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var _default = _utilsBundle.program;
|
||||||
|
exports.default = _default;
|
@ -0,0 +1,50 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Accessibility = void 0;
|
||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function axNodeFromProtocol(axNode) {
|
||||||
|
const result = {
|
||||||
|
...axNode,
|
||||||
|
value: axNode.valueNumber !== undefined ? axNode.valueNumber : axNode.valueString,
|
||||||
|
checked: axNode.checked === 'checked' ? true : axNode.checked === 'unchecked' ? false : axNode.checked,
|
||||||
|
pressed: axNode.pressed === 'pressed' ? true : axNode.pressed === 'released' ? false : axNode.pressed,
|
||||||
|
children: axNode.children ? axNode.children.map(axNodeFromProtocol) : undefined
|
||||||
|
};
|
||||||
|
delete result.valueNumber;
|
||||||
|
delete result.valueString;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
class Accessibility {
|
||||||
|
constructor(channel) {
|
||||||
|
this._channel = void 0;
|
||||||
|
this._channel = channel;
|
||||||
|
}
|
||||||
|
async snapshot(options = {}) {
|
||||||
|
const root = options.root ? options.root._elementChannel : undefined;
|
||||||
|
const result = await this._channel.accessibilitySnapshot({
|
||||||
|
interestingOnly: options.interestingOnly,
|
||||||
|
root
|
||||||
|
});
|
||||||
|
return result.rootAXNode ? axNodeFromProtocol(result.rootAXNode) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Accessibility = Accessibility;
|
@ -0,0 +1,466 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.AndroidWebView = exports.AndroidSocket = exports.AndroidInput = exports.AndroidDevice = exports.Android = void 0;
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _events = require("./events");
|
||||||
|
var _browserContext = require("./browserContext");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _timeoutSettings = require("../common/timeoutSettings");
|
||||||
|
var _waiter = require("./waiter");
|
||||||
|
var _events2 = require("events");
|
||||||
|
var _connection = require("./connection");
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
var _timeoutRunner = require("../utils/timeoutRunner");
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Android extends _channelOwner.ChannelOwner {
|
||||||
|
static from(android) {
|
||||||
|
return android._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._timeoutSettings = void 0;
|
||||||
|
this._serverLauncher = void 0;
|
||||||
|
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
|
||||||
|
}
|
||||||
|
setDefaultTimeout(timeout) {
|
||||||
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
|
this._channel.setDefaultTimeoutNoReply({
|
||||||
|
timeout
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async devices(options = {}) {
|
||||||
|
const {
|
||||||
|
devices
|
||||||
|
} = await this._channel.devices(options);
|
||||||
|
return devices.map(d => AndroidDevice.from(d));
|
||||||
|
}
|
||||||
|
async launchServer(options = {}) {
|
||||||
|
if (!this._serverLauncher) throw new Error('Launching server is not supported');
|
||||||
|
return this._serverLauncher.launchServer(options);
|
||||||
|
}
|
||||||
|
async connect(wsEndpoint, options = {}) {
|
||||||
|
return await this._wrapApiCall(async () => {
|
||||||
|
const deadline = options.timeout ? (0, _utils.monotonicTime)() + options.timeout : 0;
|
||||||
|
const headers = {
|
||||||
|
'x-playwright-browser': 'android',
|
||||||
|
...options.headers
|
||||||
|
};
|
||||||
|
const localUtils = this._connection.localUtils();
|
||||||
|
const connectParams = {
|
||||||
|
wsEndpoint,
|
||||||
|
headers,
|
||||||
|
slowMo: options.slowMo,
|
||||||
|
timeout: options.timeout
|
||||||
|
};
|
||||||
|
const {
|
||||||
|
pipe
|
||||||
|
} = await localUtils._channel.connect(connectParams);
|
||||||
|
const closePipe = () => pipe.close().catch(() => {});
|
||||||
|
const connection = new _connection.Connection(localUtils, this._instrumentation);
|
||||||
|
connection.markAsRemote();
|
||||||
|
connection.on('close', closePipe);
|
||||||
|
let device;
|
||||||
|
let closeError;
|
||||||
|
const onPipeClosed = () => {
|
||||||
|
var _device;
|
||||||
|
(_device = device) === null || _device === void 0 ? void 0 : _device._didClose();
|
||||||
|
connection.close(closeError || _errors.kBrowserClosedError);
|
||||||
|
};
|
||||||
|
pipe.on('closed', onPipeClosed);
|
||||||
|
connection.onmessage = message => pipe.send({
|
||||||
|
message
|
||||||
|
}).catch(onPipeClosed);
|
||||||
|
pipe.on('message', ({
|
||||||
|
message
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
connection.dispatch(message);
|
||||||
|
} catch (e) {
|
||||||
|
closeError = e.toString();
|
||||||
|
closePipe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const result = await (0, _timeoutRunner.raceAgainstDeadline)(async () => {
|
||||||
|
const playwright = await connection.initializePlaywright();
|
||||||
|
if (!playwright._initializer.preConnectedAndroidDevice) {
|
||||||
|
closePipe();
|
||||||
|
throw new Error('Malformed endpoint. Did you use Android.launchServer method?');
|
||||||
|
}
|
||||||
|
device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice);
|
||||||
|
device._shouldCloseConnectionOnClose = true;
|
||||||
|
device.on(_events.Events.AndroidDevice.Close, closePipe);
|
||||||
|
return device;
|
||||||
|
}, deadline);
|
||||||
|
if (!result.timedOut) {
|
||||||
|
return result.result;
|
||||||
|
} else {
|
||||||
|
closePipe();
|
||||||
|
throw new Error(`Timeout ${options.timeout}ms exceeded`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Android = Android;
|
||||||
|
class AndroidDevice extends _channelOwner.ChannelOwner {
|
||||||
|
static from(androidDevice) {
|
||||||
|
return androidDevice._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._timeoutSettings = void 0;
|
||||||
|
this._webViews = new Map();
|
||||||
|
this._shouldCloseConnectionOnClose = false;
|
||||||
|
this.input = void 0;
|
||||||
|
this.input = new AndroidInput(this);
|
||||||
|
this._timeoutSettings = new _timeoutSettings.TimeoutSettings(parent._timeoutSettings);
|
||||||
|
this._channel.on('webViewAdded', ({
|
||||||
|
webView
|
||||||
|
}) => this._onWebViewAdded(webView));
|
||||||
|
this._channel.on('webViewRemoved', ({
|
||||||
|
socketName
|
||||||
|
}) => this._onWebViewRemoved(socketName));
|
||||||
|
this._channel.on('close', () => this._didClose());
|
||||||
|
}
|
||||||
|
_onWebViewAdded(webView) {
|
||||||
|
const view = new AndroidWebView(this, webView);
|
||||||
|
this._webViews.set(webView.socketName, view);
|
||||||
|
this.emit(_events.Events.AndroidDevice.WebView, view);
|
||||||
|
}
|
||||||
|
_onWebViewRemoved(socketName) {
|
||||||
|
const view = this._webViews.get(socketName);
|
||||||
|
this._webViews.delete(socketName);
|
||||||
|
if (view) view.emit(_events.Events.AndroidWebView.Close);
|
||||||
|
}
|
||||||
|
setDefaultTimeout(timeout) {
|
||||||
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
|
this._channel.setDefaultTimeoutNoReply({
|
||||||
|
timeout
|
||||||
|
});
|
||||||
|
}
|
||||||
|
serial() {
|
||||||
|
return this._initializer.serial;
|
||||||
|
}
|
||||||
|
model() {
|
||||||
|
return this._initializer.model;
|
||||||
|
}
|
||||||
|
webViews() {
|
||||||
|
return [...this._webViews.values()];
|
||||||
|
}
|
||||||
|
async webView(selector, options) {
|
||||||
|
const predicate = v => {
|
||||||
|
if (selector.pkg) return v.pkg() === selector.pkg;
|
||||||
|
if (selector.socketName) return v._socketName() === selector.socketName;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const webView = [...this._webViews.values()].find(predicate);
|
||||||
|
if (webView) return webView;
|
||||||
|
return this.waitForEvent('webview', {
|
||||||
|
...options,
|
||||||
|
predicate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async wait(selector, options) {
|
||||||
|
await this._channel.wait({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async fill(selector, text, options) {
|
||||||
|
await this._channel.fill({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
text,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async press(selector, key, options) {
|
||||||
|
await this.tap(selector, options);
|
||||||
|
await this.input.press(key);
|
||||||
|
}
|
||||||
|
async tap(selector, options) {
|
||||||
|
await this._channel.tap({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async drag(selector, dest, options) {
|
||||||
|
await this._channel.drag({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
dest,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async fling(selector, direction, options) {
|
||||||
|
await this._channel.fling({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
direction,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async longTap(selector, options) {
|
||||||
|
await this._channel.longTap({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async pinchClose(selector, percent, options) {
|
||||||
|
await this._channel.pinchClose({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
percent,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async pinchOpen(selector, percent, options) {
|
||||||
|
await this._channel.pinchOpen({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
percent,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async scroll(selector, direction, percent, options) {
|
||||||
|
await this._channel.scroll({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
direction,
|
||||||
|
percent,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async swipe(selector, direction, percent, options) {
|
||||||
|
await this._channel.swipe({
|
||||||
|
selector: toSelectorChannel(selector),
|
||||||
|
direction,
|
||||||
|
percent,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async info(selector) {
|
||||||
|
return (await this._channel.info({
|
||||||
|
selector: toSelectorChannel(selector)
|
||||||
|
})).info;
|
||||||
|
}
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
const {
|
||||||
|
binary
|
||||||
|
} = await this._channel.screenshot();
|
||||||
|
if (options.path) await _fs.default.promises.writeFile(options.path, binary);
|
||||||
|
return binary;
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
try {
|
||||||
|
if (this._shouldCloseConnectionOnClose) this._connection.close(_errors.kBrowserClosedError);else await this._channel.close();
|
||||||
|
} catch (e) {
|
||||||
|
if ((0, _errors.isSafeCloseError)(e)) return;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_didClose() {
|
||||||
|
this.emit(_events.Events.AndroidDevice.Close, this);
|
||||||
|
}
|
||||||
|
async shell(command) {
|
||||||
|
const {
|
||||||
|
result
|
||||||
|
} = await this._channel.shell({
|
||||||
|
command
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
async open(command) {
|
||||||
|
return AndroidSocket.from((await this._channel.open({
|
||||||
|
command
|
||||||
|
})).socket);
|
||||||
|
}
|
||||||
|
async installApk(file, options) {
|
||||||
|
await this._channel.installApk({
|
||||||
|
file: await loadFile(file),
|
||||||
|
args: options && options.args
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async push(file, path, options) {
|
||||||
|
await this._channel.push({
|
||||||
|
file: await loadFile(file),
|
||||||
|
path,
|
||||||
|
mode: options ? options.mode : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async launchBrowser(options = {}) {
|
||||||
|
const contextOptions = await (0, _browserContext.prepareBrowserContextParams)(options);
|
||||||
|
const {
|
||||||
|
context
|
||||||
|
} = await this._channel.launchBrowser(contextOptions);
|
||||||
|
return _browserContext.BrowserContext.from(context);
|
||||||
|
}
|
||||||
|
async waitForEvent(event, optionsOrPredicate = {}) {
|
||||||
|
return this._wrapApiCall(async () => {
|
||||||
|
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
|
const waiter = _waiter.Waiter.createForEvent(this, event);
|
||||||
|
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
|
||||||
|
if (event !== _events.Events.AndroidDevice.Close) waiter.rejectOnEvent(this, _events.Events.AndroidDevice.Close, new Error('Device closed'));
|
||||||
|
const result = await waiter.waitForEvent(this, event, predicate);
|
||||||
|
waiter.dispose();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.AndroidDevice = AndroidDevice;
|
||||||
|
class AndroidSocket extends _channelOwner.ChannelOwner {
|
||||||
|
static from(androidDevice) {
|
||||||
|
return androidDevice._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._channel.on('data', ({
|
||||||
|
data
|
||||||
|
}) => this.emit(_events.Events.AndroidSocket.Data, data));
|
||||||
|
this._channel.on('close', () => this.emit(_events.Events.AndroidSocket.Close));
|
||||||
|
}
|
||||||
|
async write(data) {
|
||||||
|
await this._channel.write({
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
await this._channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.AndroidSocket = AndroidSocket;
|
||||||
|
async function loadFile(file) {
|
||||||
|
if ((0, _utils.isString)(file)) return _fs.default.promises.readFile(file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
class AndroidInput {
|
||||||
|
constructor(device) {
|
||||||
|
this._device = void 0;
|
||||||
|
this._device = device;
|
||||||
|
}
|
||||||
|
async type(text) {
|
||||||
|
await this._device._channel.inputType({
|
||||||
|
text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async press(key) {
|
||||||
|
await this._device._channel.inputPress({
|
||||||
|
key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async tap(point) {
|
||||||
|
await this._device._channel.inputTap({
|
||||||
|
point
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async swipe(from, segments, steps) {
|
||||||
|
await this._device._channel.inputSwipe({
|
||||||
|
segments,
|
||||||
|
steps
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async drag(from, to, steps) {
|
||||||
|
await this._device._channel.inputDrag({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
steps
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.AndroidInput = AndroidInput;
|
||||||
|
function toSelectorChannel(selector) {
|
||||||
|
const {
|
||||||
|
checkable,
|
||||||
|
checked,
|
||||||
|
clazz,
|
||||||
|
clickable,
|
||||||
|
depth,
|
||||||
|
desc,
|
||||||
|
enabled,
|
||||||
|
focusable,
|
||||||
|
focused,
|
||||||
|
hasChild,
|
||||||
|
hasDescendant,
|
||||||
|
longClickable,
|
||||||
|
pkg,
|
||||||
|
res,
|
||||||
|
scrollable,
|
||||||
|
selected,
|
||||||
|
text
|
||||||
|
} = selector;
|
||||||
|
const toRegex = value => {
|
||||||
|
if (value === undefined) return undefined;
|
||||||
|
if ((0, _utils.isRegExp)(value)) return value.source;
|
||||||
|
return '^' + value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d') + '$';
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
checkable,
|
||||||
|
checked,
|
||||||
|
clazz: toRegex(clazz),
|
||||||
|
pkg: toRegex(pkg),
|
||||||
|
desc: toRegex(desc),
|
||||||
|
res: toRegex(res),
|
||||||
|
text: toRegex(text),
|
||||||
|
clickable,
|
||||||
|
depth,
|
||||||
|
enabled,
|
||||||
|
focusable,
|
||||||
|
focused,
|
||||||
|
hasChild: hasChild ? {
|
||||||
|
selector: toSelectorChannel(hasChild.selector)
|
||||||
|
} : undefined,
|
||||||
|
hasDescendant: hasDescendant ? {
|
||||||
|
selector: toSelectorChannel(hasDescendant.selector),
|
||||||
|
maxDepth: hasDescendant.maxDepth
|
||||||
|
} : undefined,
|
||||||
|
longClickable,
|
||||||
|
scrollable,
|
||||||
|
selected
|
||||||
|
};
|
||||||
|
}
|
||||||
|
class AndroidWebView extends _events2.EventEmitter {
|
||||||
|
constructor(device, data) {
|
||||||
|
super();
|
||||||
|
this._device = void 0;
|
||||||
|
this._data = void 0;
|
||||||
|
this._pagePromise = void 0;
|
||||||
|
this._device = device;
|
||||||
|
this._data = data;
|
||||||
|
}
|
||||||
|
pid() {
|
||||||
|
return this._data.pid;
|
||||||
|
}
|
||||||
|
pkg() {
|
||||||
|
return this._data.pkg;
|
||||||
|
}
|
||||||
|
_socketName() {
|
||||||
|
return this._data.socketName;
|
||||||
|
}
|
||||||
|
async page() {
|
||||||
|
if (!this._pagePromise) this._pagePromise = this._fetchPage();
|
||||||
|
return this._pagePromise;
|
||||||
|
}
|
||||||
|
async _fetchPage() {
|
||||||
|
const {
|
||||||
|
context
|
||||||
|
} = await this._device._channel.connectToWebView({
|
||||||
|
socketName: this._data.socketName
|
||||||
|
});
|
||||||
|
return _browserContext.BrowserContext.from(context).pages()[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.AndroidWebView = AndroidWebView;
|
@ -0,0 +1,265 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "APIRequest", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _fetch.APIRequest;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "APIRequestContext", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _fetch.APIRequestContext;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "APIResponse", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _fetch.APIResponse;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Accessibility", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _accessibility.Accessibility;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Android", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _android.Android;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "AndroidDevice", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _android.AndroidDevice;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "AndroidInput", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _android.AndroidInput;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "AndroidSocket", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _android.AndroidSocket;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "AndroidWebView", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _android.AndroidWebView;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Browser", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _browser.Browser;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "BrowserContext", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _browserContext.BrowserContext;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "BrowserType", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _browserType.BrowserType;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "CDPSession", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _cdpSession.CDPSession;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "ConsoleMessage", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _consoleMessage.ConsoleMessage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Coverage", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _coverage.Coverage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Dialog", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _dialog.Dialog;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Download", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _download.Download;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Electron", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _electron.Electron;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "ElectronApplication", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _electron.ElectronApplication;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "ElementHandle", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _elementHandle.ElementHandle;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "FileChooser", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _fileChooser.FileChooser;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Frame", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _frame.Frame;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "FrameLocator", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _locator.FrameLocator;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "JSHandle", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _jsHandle.JSHandle;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Keyboard", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _input.Keyboard;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Locator", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _locator.Locator;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Mouse", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _input.Mouse;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Page", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _page.Page;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Playwright", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _playwright.Playwright;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Request", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _network.Request;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Response", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _network.Response;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Route", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _network.Route;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Selectors", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _selectors.Selectors;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "TimeoutError", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _errors.TimeoutError;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Touchscreen", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _input.Touchscreen;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Tracing", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _tracing.Tracing;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Video", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _video.Video;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "WebSocket", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _network.WebSocket;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(exports, "Worker", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return _worker.Worker;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var _accessibility = require("./accessibility");
|
||||||
|
var _android = require("./android");
|
||||||
|
var _browser = require("./browser");
|
||||||
|
var _browserContext = require("./browserContext");
|
||||||
|
var _browserType = require("./browserType");
|
||||||
|
var _consoleMessage = require("./consoleMessage");
|
||||||
|
var _coverage = require("./coverage");
|
||||||
|
var _dialog = require("./dialog");
|
||||||
|
var _download = require("./download");
|
||||||
|
var _electron = require("./electron");
|
||||||
|
var _locator = require("./locator");
|
||||||
|
var _elementHandle = require("./elementHandle");
|
||||||
|
var _fileChooser = require("./fileChooser");
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
var _frame = require("./frame");
|
||||||
|
var _input = require("./input");
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _network = require("./network");
|
||||||
|
var _fetch = require("./fetch");
|
||||||
|
var _page = require("./page");
|
||||||
|
var _selectors = require("./selectors");
|
||||||
|
var _tracing = require("./tracing");
|
||||||
|
var _video = require("./video");
|
||||||
|
var _worker = require("./worker");
|
||||||
|
var _cdpSession = require("./cdpSession");
|
||||||
|
var _playwright = require("./playwright");
|
@ -0,0 +1,80 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Artifact = void 0;
|
||||||
|
var fs = _interopRequireWildcard(require("fs"));
|
||||||
|
var _stream = require("./stream");
|
||||||
|
var _fileUtils = require("../utils/fileUtils");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Artifact extends _channelOwner.ChannelOwner {
|
||||||
|
static from(channel) {
|
||||||
|
return channel._object;
|
||||||
|
}
|
||||||
|
async pathAfterFinished() {
|
||||||
|
if (this._connection.isRemote()) throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
|
||||||
|
return (await this._channel.pathAfterFinished()).value || null;
|
||||||
|
}
|
||||||
|
async saveAs(path) {
|
||||||
|
if (!this._connection.isRemote()) {
|
||||||
|
await this._channel.saveAs({
|
||||||
|
path
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await this._channel.saveAsStream();
|
||||||
|
const stream = _stream.Stream.from(result.stream);
|
||||||
|
await (0, _fileUtils.mkdirIfNeeded)(path);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
stream.stream().pipe(fs.createWriteStream(path)).on('finish', resolve).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async failure() {
|
||||||
|
return (await this._channel.failure()).error || null;
|
||||||
|
}
|
||||||
|
async createReadStream() {
|
||||||
|
const result = await this._channel.stream();
|
||||||
|
if (!result.stream) return null;
|
||||||
|
const stream = _stream.Stream.from(result.stream);
|
||||||
|
return stream.stream();
|
||||||
|
}
|
||||||
|
async readIntoBuffer() {
|
||||||
|
const stream = await this.createReadStream();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const chunks = [];
|
||||||
|
stream.on('data', chunk => {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
stream.on('end', () => {
|
||||||
|
resolve(Buffer.concat(chunks));
|
||||||
|
});
|
||||||
|
stream.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async cancel() {
|
||||||
|
return this._channel.cancel();
|
||||||
|
}
|
||||||
|
async delete() {
|
||||||
|
return this._channel.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Artifact = Artifact;
|
@ -0,0 +1,133 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Browser = void 0;
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _browserContext = require("./browserContext");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _events = require("./events");
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
var _cdpSession = require("./cdpSession");
|
||||||
|
var _artifact = require("./artifact");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Browser extends _channelOwner.ChannelOwner {
|
||||||
|
// Used from @playwright/test fixtures.
|
||||||
|
|
||||||
|
static from(browser) {
|
||||||
|
return browser._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._contexts = new Set();
|
||||||
|
this._isConnected = true;
|
||||||
|
this._closedPromise = void 0;
|
||||||
|
this._shouldCloseConnectionOnClose = false;
|
||||||
|
this._browserType = void 0;
|
||||||
|
this._options = {};
|
||||||
|
this._name = void 0;
|
||||||
|
this._path = void 0;
|
||||||
|
this._connectHeaders = void 0;
|
||||||
|
this._name = initializer.name;
|
||||||
|
this._channel.on('close', () => this._didClose());
|
||||||
|
this._closedPromise = new Promise(f => this.once(_events.Events.Browser.Disconnected, f));
|
||||||
|
}
|
||||||
|
browserType() {
|
||||||
|
return this._browserType;
|
||||||
|
}
|
||||||
|
async newContext(options = {}) {
|
||||||
|
return await this._innerNewContext(options, false);
|
||||||
|
}
|
||||||
|
async _newContextForReuse(options = {}) {
|
||||||
|
return await this._wrapApiCall(async () => {
|
||||||
|
for (const context of this._contexts) {
|
||||||
|
await this._browserType._willCloseContext(context);
|
||||||
|
for (const page of context.pages()) page._onClose();
|
||||||
|
context._onClose();
|
||||||
|
}
|
||||||
|
return await this._innerNewContext(options, true);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
async _innerNewContext(options = {}, forReuse) {
|
||||||
|
options = {
|
||||||
|
...this._browserType._defaultContextOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
const contextOptions = await (0, _browserContext.prepareBrowserContextParams)(options);
|
||||||
|
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
|
||||||
|
const context = _browserContext.BrowserContext.from(response.context);
|
||||||
|
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
contexts() {
|
||||||
|
return [...this._contexts];
|
||||||
|
}
|
||||||
|
version() {
|
||||||
|
return this._initializer.version;
|
||||||
|
}
|
||||||
|
async newPage(options = {}) {
|
||||||
|
return await this._wrapApiCall(async () => {
|
||||||
|
const context = await this.newContext(options);
|
||||||
|
const page = await context.newPage();
|
||||||
|
page._ownedContext = context;
|
||||||
|
context._ownerPage = page;
|
||||||
|
return page;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isConnected() {
|
||||||
|
return this._isConnected;
|
||||||
|
}
|
||||||
|
async newBrowserCDPSession() {
|
||||||
|
return _cdpSession.CDPSession.from((await this._channel.newBrowserCDPSession()).session);
|
||||||
|
}
|
||||||
|
async startTracing(page, options = {}) {
|
||||||
|
this._path = options.path;
|
||||||
|
await this._channel.startTracing({
|
||||||
|
...options,
|
||||||
|
page: page ? page._channel : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async stopTracing() {
|
||||||
|
const artifact = _artifact.Artifact.from((await this._channel.stopTracing()).artifact);
|
||||||
|
const buffer = await artifact.readIntoBuffer();
|
||||||
|
await artifact.delete();
|
||||||
|
if (this._path) {
|
||||||
|
await (0, _utils.mkdirIfNeeded)(this._path);
|
||||||
|
await _fs.default.promises.writeFile(this._path, buffer);
|
||||||
|
this._path = undefined;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
try {
|
||||||
|
if (this._shouldCloseConnectionOnClose) this._connection.close(_errors.kBrowserClosedError);else await this._channel.close();
|
||||||
|
await this._closedPromise;
|
||||||
|
} catch (e) {
|
||||||
|
if ((0, _errors.isSafeCloseError)(e)) return;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_didClose() {
|
||||||
|
this._isConnected = false;
|
||||||
|
this.emit(_events.Events.Browser.Disconnected, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Browser = Browser;
|
@ -0,0 +1,452 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.BrowserContext = void 0;
|
||||||
|
exports.prepareBrowserContextParams = prepareBrowserContextParams;
|
||||||
|
var _page = require("./page");
|
||||||
|
var _frame = require("./frame");
|
||||||
|
var network = _interopRequireWildcard(require("./network"));
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _clientHelper = require("./clientHelper");
|
||||||
|
var _browser = require("./browser");
|
||||||
|
var _worker = require("./worker");
|
||||||
|
var _events = require("./events");
|
||||||
|
var _timeoutSettings = require("../common/timeoutSettings");
|
||||||
|
var _waiter = require("./waiter");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _fileUtils = require("../utils/fileUtils");
|
||||||
|
var _cdpSession = require("./cdpSession");
|
||||||
|
var _tracing = require("./tracing");
|
||||||
|
var _artifact = require("./artifact");
|
||||||
|
var _fetch = require("./fetch");
|
||||||
|
var _stackTrace = require("../utils/stackTrace");
|
||||||
|
var _harRouter = require("./harRouter");
|
||||||
|
var _consoleMessage = require("./consoleMessage");
|
||||||
|
var _dialog = require("./dialog");
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class BrowserContext extends _channelOwner.ChannelOwner {
|
||||||
|
static from(context) {
|
||||||
|
return context._object;
|
||||||
|
}
|
||||||
|
static fromNullable(context) {
|
||||||
|
return context ? BrowserContext.from(context) : null;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
var _this$_browser, _this$_browser2;
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._pages = new Set();
|
||||||
|
this._routes = [];
|
||||||
|
this._browser = null;
|
||||||
|
this._browserType = void 0;
|
||||||
|
this._bindings = new Map();
|
||||||
|
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
|
||||||
|
this._ownerPage = void 0;
|
||||||
|
this._closedPromise = void 0;
|
||||||
|
this._options = {};
|
||||||
|
this.request = void 0;
|
||||||
|
this.tracing = void 0;
|
||||||
|
this._backgroundPages = new Set();
|
||||||
|
this._serviceWorkers = new Set();
|
||||||
|
this._isChromium = void 0;
|
||||||
|
this._harRecorders = new Map();
|
||||||
|
this._closeWasCalled = false;
|
||||||
|
if (parent instanceof _browser.Browser) this._browser = parent;
|
||||||
|
(_this$_browser = this._browser) === null || _this$_browser === void 0 ? void 0 : _this$_browser._contexts.add(this);
|
||||||
|
this._isChromium = ((_this$_browser2 = this._browser) === null || _this$_browser2 === void 0 ? void 0 : _this$_browser2._name) === 'chromium';
|
||||||
|
this.tracing = _tracing.Tracing.from(initializer.tracing);
|
||||||
|
this.request = _fetch.APIRequestContext.from(initializer.requestContext);
|
||||||
|
this._channel.on('bindingCall', ({
|
||||||
|
binding
|
||||||
|
}) => this._onBinding(_page.BindingCall.from(binding)));
|
||||||
|
this._channel.on('close', () => this._onClose());
|
||||||
|
this._channel.on('page', ({
|
||||||
|
page
|
||||||
|
}) => this._onPage(_page.Page.from(page)));
|
||||||
|
this._channel.on('route', ({
|
||||||
|
route
|
||||||
|
}) => this._onRoute(network.Route.from(route)));
|
||||||
|
this._channel.on('backgroundPage', ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
const backgroundPage = _page.Page.from(page);
|
||||||
|
this._backgroundPages.add(backgroundPage);
|
||||||
|
this.emit(_events.Events.BrowserContext.BackgroundPage, backgroundPage);
|
||||||
|
});
|
||||||
|
this._channel.on('serviceWorker', ({
|
||||||
|
worker
|
||||||
|
}) => {
|
||||||
|
const serviceWorker = _worker.Worker.from(worker);
|
||||||
|
serviceWorker._context = this;
|
||||||
|
this._serviceWorkers.add(serviceWorker);
|
||||||
|
this.emit(_events.Events.BrowserContext.ServiceWorker, serviceWorker);
|
||||||
|
});
|
||||||
|
this._channel.on('console', ({
|
||||||
|
message
|
||||||
|
}) => {
|
||||||
|
const consoleMessage = _consoleMessage.ConsoleMessage.from(message);
|
||||||
|
this.emit(_events.Events.BrowserContext.Console, consoleMessage);
|
||||||
|
const page = consoleMessage.page();
|
||||||
|
if (page) page.emit(_events.Events.Page.Console, consoleMessage);
|
||||||
|
});
|
||||||
|
this._channel.on('dialog', ({
|
||||||
|
dialog
|
||||||
|
}) => {
|
||||||
|
const dialogObject = _dialog.Dialog.from(dialog);
|
||||||
|
let hasListeners = this.emit(_events.Events.BrowserContext.Dialog, dialogObject);
|
||||||
|
const page = dialogObject.page();
|
||||||
|
if (page) hasListeners = page.emit(_events.Events.Page.Dialog, dialogObject) || hasListeners;
|
||||||
|
if (!hasListeners) {
|
||||||
|
// Although we do similar handling on the server side, we still need this logic
|
||||||
|
// on the client side due to a possible race condition between two async calls:
|
||||||
|
// a) removing "dialog" listener subscription (client->server)
|
||||||
|
// b) actual "dialog" event (server->client)
|
||||||
|
if (dialogObject.type() === 'beforeunload') dialog.accept({}).catch(() => {});else dialog.dismiss().catch(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._channel.on('request', ({
|
||||||
|
request,
|
||||||
|
page
|
||||||
|
}) => this._onRequest(network.Request.from(request), _page.Page.fromNullable(page)));
|
||||||
|
this._channel.on('requestFailed', ({
|
||||||
|
request,
|
||||||
|
failureText,
|
||||||
|
responseEndTiming,
|
||||||
|
page
|
||||||
|
}) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, _page.Page.fromNullable(page)));
|
||||||
|
this._channel.on('requestFinished', params => this._onRequestFinished(params));
|
||||||
|
this._channel.on('response', ({
|
||||||
|
response,
|
||||||
|
page
|
||||||
|
}) => this._onResponse(network.Response.from(response), _page.Page.fromNullable(page)));
|
||||||
|
this._closedPromise = new Promise(f => this.once(_events.Events.BrowserContext.Close, f));
|
||||||
|
this._setEventToSubscriptionMapping(new Map([[_events.Events.BrowserContext.Console, 'console'], [_events.Events.BrowserContext.Dialog, 'dialog'], [_events.Events.BrowserContext.Request, 'request'], [_events.Events.BrowserContext.Response, 'response'], [_events.Events.BrowserContext.RequestFinished, 'requestFinished'], [_events.Events.BrowserContext.RequestFailed, 'requestFailed']]));
|
||||||
|
}
|
||||||
|
_setOptions(contextOptions, browserOptions) {
|
||||||
|
this._options = contextOptions;
|
||||||
|
if (this._options.recordHar) this._harRecorders.set('', {
|
||||||
|
path: this._options.recordHar.path,
|
||||||
|
content: this._options.recordHar.content
|
||||||
|
});
|
||||||
|
this.tracing._tracesDir = browserOptions.tracesDir;
|
||||||
|
}
|
||||||
|
_onPage(page) {
|
||||||
|
this._pages.add(page);
|
||||||
|
this.emit(_events.Events.BrowserContext.Page, page);
|
||||||
|
if (page._opener && !page._opener.isClosed()) page._opener.emit(_events.Events.Page.Popup, page);
|
||||||
|
}
|
||||||
|
_onRequest(request, page) {
|
||||||
|
this.emit(_events.Events.BrowserContext.Request, request);
|
||||||
|
if (page) page.emit(_events.Events.Page.Request, request);
|
||||||
|
}
|
||||||
|
_onResponse(response, page) {
|
||||||
|
this.emit(_events.Events.BrowserContext.Response, response);
|
||||||
|
if (page) page.emit(_events.Events.Page.Response, response);
|
||||||
|
}
|
||||||
|
_onRequestFailed(request, responseEndTiming, failureText, page) {
|
||||||
|
request._failureText = failureText || null;
|
||||||
|
request._setResponseEndTiming(responseEndTiming);
|
||||||
|
this.emit(_events.Events.BrowserContext.RequestFailed, request);
|
||||||
|
if (page) page.emit(_events.Events.Page.RequestFailed, request);
|
||||||
|
}
|
||||||
|
_onRequestFinished(params) {
|
||||||
|
const {
|
||||||
|
responseEndTiming
|
||||||
|
} = params;
|
||||||
|
const request = network.Request.from(params.request);
|
||||||
|
const response = network.Response.fromNullable(params.response);
|
||||||
|
const page = _page.Page.fromNullable(params.page);
|
||||||
|
request._setResponseEndTiming(responseEndTiming);
|
||||||
|
this.emit(_events.Events.BrowserContext.RequestFinished, request);
|
||||||
|
if (page) page.emit(_events.Events.Page.RequestFinished, request);
|
||||||
|
if (response) response._finishedPromise.resolve(null);
|
||||||
|
}
|
||||||
|
async _onRoute(route) {
|
||||||
|
const routeHandlers = this._routes.slice();
|
||||||
|
for (const routeHandler of routeHandlers) {
|
||||||
|
if (!routeHandler.matches(route.request().url())) continue;
|
||||||
|
if (routeHandler.willExpire()) this._routes.splice(this._routes.indexOf(routeHandler), 1);
|
||||||
|
const handled = await routeHandler.handle(route);
|
||||||
|
if (!this._routes.length) this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {});
|
||||||
|
if (handled) return;
|
||||||
|
}
|
||||||
|
await route._innerContinue(true);
|
||||||
|
}
|
||||||
|
async _onBinding(bindingCall) {
|
||||||
|
const func = this._bindings.get(bindingCall._initializer.name);
|
||||||
|
if (!func) return;
|
||||||
|
await bindingCall.call(func);
|
||||||
|
}
|
||||||
|
setDefaultNavigationTimeout(timeout) {
|
||||||
|
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||||
|
this._wrapApiCall(async () => {
|
||||||
|
this._channel.setDefaultNavigationTimeoutNoReply({
|
||||||
|
timeout
|
||||||
|
}).catch(() => {});
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
setDefaultTimeout(timeout) {
|
||||||
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
|
this._wrapApiCall(async () => {
|
||||||
|
this._channel.setDefaultTimeoutNoReply({
|
||||||
|
timeout
|
||||||
|
}).catch(() => {});
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
browser() {
|
||||||
|
return this._browser;
|
||||||
|
}
|
||||||
|
pages() {
|
||||||
|
return [...this._pages];
|
||||||
|
}
|
||||||
|
async newPage() {
|
||||||
|
if (this._ownerPage) throw new Error('Please use browser.newContext()');
|
||||||
|
return _page.Page.from((await this._channel.newPage()).page);
|
||||||
|
}
|
||||||
|
async cookies(urls) {
|
||||||
|
if (!urls) urls = [];
|
||||||
|
if (urls && typeof urls === 'string') urls = [urls];
|
||||||
|
return (await this._channel.cookies({
|
||||||
|
urls: urls
|
||||||
|
})).cookies;
|
||||||
|
}
|
||||||
|
async addCookies(cookies) {
|
||||||
|
await this._channel.addCookies({
|
||||||
|
cookies
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async clearCookies() {
|
||||||
|
await this._channel.clearCookies();
|
||||||
|
}
|
||||||
|
async grantPermissions(permissions, options) {
|
||||||
|
await this._channel.grantPermissions({
|
||||||
|
permissions,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async clearPermissions() {
|
||||||
|
await this._channel.clearPermissions();
|
||||||
|
}
|
||||||
|
async setGeolocation(geolocation) {
|
||||||
|
await this._channel.setGeolocation({
|
||||||
|
geolocation: geolocation || undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async setExtraHTTPHeaders(headers) {
|
||||||
|
network.validateHeaders(headers);
|
||||||
|
await this._channel.setExtraHTTPHeaders({
|
||||||
|
headers: (0, _utils.headersObjectToArray)(headers)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async setOffline(offline) {
|
||||||
|
await this._channel.setOffline({
|
||||||
|
offline
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async setHTTPCredentials(httpCredentials) {
|
||||||
|
await this._channel.setHTTPCredentials({
|
||||||
|
httpCredentials: httpCredentials || undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async addInitScript(script, arg) {
|
||||||
|
const source = await (0, _clientHelper.evaluationScript)(script, arg);
|
||||||
|
await this._channel.addInitScript({
|
||||||
|
source
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async exposeBinding(name, callback, options = {}) {
|
||||||
|
await this._channel.exposeBinding({
|
||||||
|
name,
|
||||||
|
needsHandle: options.handle
|
||||||
|
});
|
||||||
|
this._bindings.set(name, callback);
|
||||||
|
}
|
||||||
|
async exposeFunction(name, callback) {
|
||||||
|
await this._channel.exposeBinding({
|
||||||
|
name
|
||||||
|
});
|
||||||
|
const binding = (source, ...args) => callback(...args);
|
||||||
|
this._bindings.set(name, binding);
|
||||||
|
}
|
||||||
|
async route(url, handler, options = {}) {
|
||||||
|
this._routes.unshift(new network.RouteHandler(this._options.baseURL, url, handler, options.times));
|
||||||
|
await this._updateInterceptionPatterns();
|
||||||
|
}
|
||||||
|
async _recordIntoHAR(har, page, options = {}) {
|
||||||
|
var _options$updateConten, _options$updateMode, _options$updateConten2;
|
||||||
|
const {
|
||||||
|
harId
|
||||||
|
} = await this._channel.harStart({
|
||||||
|
page: page === null || page === void 0 ? void 0 : page._channel,
|
||||||
|
options: prepareRecordHarOptions({
|
||||||
|
path: har,
|
||||||
|
content: (_options$updateConten = options.updateContent) !== null && _options$updateConten !== void 0 ? _options$updateConten : 'attach',
|
||||||
|
mode: (_options$updateMode = options.updateMode) !== null && _options$updateMode !== void 0 ? _options$updateMode : 'minimal',
|
||||||
|
urlFilter: options.url
|
||||||
|
})
|
||||||
|
});
|
||||||
|
this._harRecorders.set(harId, {
|
||||||
|
path: har,
|
||||||
|
content: (_options$updateConten2 = options.updateContent) !== null && _options$updateConten2 !== void 0 ? _options$updateConten2 : 'attach'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async routeFromHAR(har, options = {}) {
|
||||||
|
if (options.update) {
|
||||||
|
await this._recordIntoHAR(har, null, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const harRouter = await _harRouter.HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', {
|
||||||
|
urlMatch: options.url
|
||||||
|
});
|
||||||
|
harRouter.addContextRoute(this);
|
||||||
|
}
|
||||||
|
async unroute(url, handler) {
|
||||||
|
this._routes = this._routes.filter(route => !(0, _utils.urlMatchesEqual)(route.url, url) || handler && route.handler !== handler);
|
||||||
|
await this._updateInterceptionPatterns();
|
||||||
|
}
|
||||||
|
async _updateInterceptionPatterns() {
|
||||||
|
const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes);
|
||||||
|
await this._channel.setNetworkInterceptionPatterns({
|
||||||
|
patterns
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async waitForEvent(event, optionsOrPredicate = {}) {
|
||||||
|
return this._wrapApiCall(async () => {
|
||||||
|
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
|
const waiter = _waiter.Waiter.createForEvent(this, event);
|
||||||
|
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
|
||||||
|
if (event !== _events.Events.BrowserContext.Close) waiter.rejectOnEvent(this, _events.Events.BrowserContext.Close, new Error('Context closed'));
|
||||||
|
const result = await waiter.waitForEvent(this, event, predicate);
|
||||||
|
waiter.dispose();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async storageState(options = {}) {
|
||||||
|
const state = await this._channel.storageState();
|
||||||
|
if (options.path) {
|
||||||
|
await (0, _fileUtils.mkdirIfNeeded)(options.path);
|
||||||
|
await _fs.default.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
backgroundPages() {
|
||||||
|
return [...this._backgroundPages];
|
||||||
|
}
|
||||||
|
serviceWorkers() {
|
||||||
|
return [...this._serviceWorkers];
|
||||||
|
}
|
||||||
|
async newCDPSession(page) {
|
||||||
|
// channelOwner.ts's validation messages don't handle the pseudo-union type, so we're explicit here
|
||||||
|
if (!(page instanceof _page.Page) && !(page instanceof _frame.Frame)) throw new Error('page: expected Page or Frame');
|
||||||
|
const result = await this._channel.newCDPSession(page instanceof _page.Page ? {
|
||||||
|
page: page._channel
|
||||||
|
} : {
|
||||||
|
frame: page._channel
|
||||||
|
});
|
||||||
|
return _cdpSession.CDPSession.from(result.session);
|
||||||
|
}
|
||||||
|
_onClose() {
|
||||||
|
var _this$_browserType, _this$_browserType$_c;
|
||||||
|
if (this._browser) this._browser._contexts.delete(this);
|
||||||
|
(_this$_browserType = this._browserType) === null || _this$_browserType === void 0 ? void 0 : (_this$_browserType$_c = _this$_browserType._contexts) === null || _this$_browserType$_c === void 0 ? void 0 : _this$_browserType$_c.delete(this);
|
||||||
|
this.emit(_events.Events.BrowserContext.Close, this);
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
if (this._closeWasCalled) return;
|
||||||
|
this._closeWasCalled = true;
|
||||||
|
await this._wrapApiCall(async () => {
|
||||||
|
var _this$_browserType2;
|
||||||
|
await ((_this$_browserType2 = this._browserType) === null || _this$_browserType2 === void 0 ? void 0 : _this$_browserType2._willCloseContext(this));
|
||||||
|
for (const [harId, harParams] of this._harRecorders) {
|
||||||
|
const har = await this._channel.harExport({
|
||||||
|
harId
|
||||||
|
});
|
||||||
|
const artifact = _artifact.Artifact.from(har.artifact);
|
||||||
|
// Server side will compress artifact if content is attach or if file is .zip.
|
||||||
|
const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
|
||||||
|
const needCompressed = harParams.path.endsWith('.zip');
|
||||||
|
if (isCompressed && !needCompressed) {
|
||||||
|
await artifact.saveAs(harParams.path + '.tmp');
|
||||||
|
await this._connection.localUtils()._channel.harUnzip({
|
||||||
|
zipFile: harParams.path + '.tmp',
|
||||||
|
harFile: harParams.path
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await artifact.saveAs(harParams.path);
|
||||||
|
}
|
||||||
|
await artifact.delete();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
await this._channel.close();
|
||||||
|
await this._closedPromise;
|
||||||
|
}
|
||||||
|
async _enableRecorder(params) {
|
||||||
|
await this._channel.recorderSupplementEnable(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.BrowserContext = BrowserContext;
|
||||||
|
async function prepareStorageState(options) {
|
||||||
|
if (typeof options.storageState !== 'string') return options.storageState;
|
||||||
|
try {
|
||||||
|
return JSON.parse(await _fs.default.promises.readFile(options.storageState, 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
(0, _stackTrace.rewriteErrorMessage)(e, `Error reading storage state from ${options.storageState}:\n` + e.message);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function prepareRecordHarOptions(options) {
|
||||||
|
if (!options) return;
|
||||||
|
return {
|
||||||
|
path: options.path,
|
||||||
|
content: options.content || (options.omitContent ? 'omit' : undefined),
|
||||||
|
urlGlob: (0, _utils.isString)(options.urlFilter) ? options.urlFilter : undefined,
|
||||||
|
urlRegexSource: (0, _utils.isRegExp)(options.urlFilter) ? options.urlFilter.source : undefined,
|
||||||
|
urlRegexFlags: (0, _utils.isRegExp)(options.urlFilter) ? options.urlFilter.flags : undefined,
|
||||||
|
mode: options.mode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function prepareBrowserContextParams(options) {
|
||||||
|
if (options.videoSize && !options.videosPath) throw new Error(`"videoSize" option requires "videosPath" to be specified`);
|
||||||
|
if (options.extraHTTPHeaders) network.validateHeaders(options.extraHTTPHeaders);
|
||||||
|
const contextParams = {
|
||||||
|
...options,
|
||||||
|
viewport: options.viewport === null ? undefined : options.viewport,
|
||||||
|
noDefaultViewport: options.viewport === null,
|
||||||
|
extraHTTPHeaders: options.extraHTTPHeaders ? (0, _utils.headersObjectToArray)(options.extraHTTPHeaders) : undefined,
|
||||||
|
storageState: await prepareStorageState(options),
|
||||||
|
serviceWorkers: options.serviceWorkers,
|
||||||
|
recordHar: prepareRecordHarOptions(options.recordHar),
|
||||||
|
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
|
||||||
|
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
|
||||||
|
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors
|
||||||
|
};
|
||||||
|
if (!contextParams.recordVideo && options.videosPath) {
|
||||||
|
contextParams.recordVideo = {
|
||||||
|
dir: options.videosPath,
|
||||||
|
size: options.videoSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return contextParams;
|
||||||
|
}
|
@ -0,0 +1,254 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.BrowserType = void 0;
|
||||||
|
var _browser3 = require("./browser");
|
||||||
|
var _browserContext = require("./browserContext");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _connection = require("./connection");
|
||||||
|
var _events = require("./events");
|
||||||
|
var _clientHelper = require("./clientHelper");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
var _timeoutRunner = require("../utils/timeoutRunner");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This is here just for api generation and checking.
|
||||||
|
|
||||||
|
class BrowserType extends _channelOwner.ChannelOwner {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this._serverLauncher = void 0;
|
||||||
|
this._contexts = new Set();
|
||||||
|
this._playwright = void 0;
|
||||||
|
// Instrumentation.
|
||||||
|
this._defaultContextOptions = void 0;
|
||||||
|
this._defaultContextTimeout = void 0;
|
||||||
|
this._defaultContextNavigationTimeout = void 0;
|
||||||
|
this._defaultLaunchOptions = void 0;
|
||||||
|
this._defaultConnectOptions = void 0;
|
||||||
|
}
|
||||||
|
static from(browserType) {
|
||||||
|
return browserType._object;
|
||||||
|
}
|
||||||
|
executablePath() {
|
||||||
|
if (!this._initializer.executablePath) throw new Error('Browser is not supported on current platform');
|
||||||
|
return this._initializer.executablePath;
|
||||||
|
}
|
||||||
|
name() {
|
||||||
|
return this._initializer.name;
|
||||||
|
}
|
||||||
|
async launch(options = {}) {
|
||||||
|
var _this$_defaultLaunchO;
|
||||||
|
(0, _utils.assert)(!options.userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
|
||||||
|
(0, _utils.assert)(!options.port, 'Cannot specify a port without launching as a server.');
|
||||||
|
if (this._defaultConnectOptions) return await this._connectInsteadOfLaunching(this._defaultConnectOptions, options);
|
||||||
|
const logger = options.logger || ((_this$_defaultLaunchO = this._defaultLaunchOptions) === null || _this$_defaultLaunchO === void 0 ? void 0 : _this$_defaultLaunchO.logger);
|
||||||
|
options = {
|
||||||
|
...this._defaultLaunchOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
const launchOptions = {
|
||||||
|
...options,
|
||||||
|
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
||||||
|
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||||
|
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined
|
||||||
|
};
|
||||||
|
return await this._wrapApiCall(async () => {
|
||||||
|
const browser = _browser3.Browser.from((await this._channel.launch(launchOptions)).browser);
|
||||||
|
this._didLaunchBrowser(browser, options, logger);
|
||||||
|
return browser;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _connectInsteadOfLaunching(connectOptions, launchOptions) {
|
||||||
|
var _connectOptions$expos, _connectOptions$timeo;
|
||||||
|
return this._connect({
|
||||||
|
wsEndpoint: connectOptions.wsEndpoint,
|
||||||
|
headers: {
|
||||||
|
// HTTP headers are ASCII only (not UTF-8).
|
||||||
|
'x-playwright-launch-options': (0, _utils.jsonStringifyForceASCII)({
|
||||||
|
...this._defaultLaunchOptions,
|
||||||
|
...launchOptions
|
||||||
|
}),
|
||||||
|
...connectOptions.headers
|
||||||
|
},
|
||||||
|
exposeNetwork: (_connectOptions$expos = connectOptions.exposeNetwork) !== null && _connectOptions$expos !== void 0 ? _connectOptions$expos : connectOptions._exposeNetwork,
|
||||||
|
slowMo: connectOptions.slowMo,
|
||||||
|
timeout: (_connectOptions$timeo = connectOptions.timeout) !== null && _connectOptions$timeo !== void 0 ? _connectOptions$timeo : 3 * 60 * 1000 // 3 minutes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async launchServer(options = {}) {
|
||||||
|
if (!this._serverLauncher) throw new Error('Launching server is not supported');
|
||||||
|
options = {
|
||||||
|
...this._defaultLaunchOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
return this._serverLauncher.launchServer(options);
|
||||||
|
}
|
||||||
|
async launchPersistentContext(userDataDir, options = {}) {
|
||||||
|
var _this$_defaultLaunchO2;
|
||||||
|
const logger = options.logger || ((_this$_defaultLaunchO2 = this._defaultLaunchOptions) === null || _this$_defaultLaunchO2 === void 0 ? void 0 : _this$_defaultLaunchO2.logger);
|
||||||
|
(0, _utils.assert)(!options.port, 'Cannot specify a port without launching as a server.');
|
||||||
|
options = {
|
||||||
|
...this._defaultLaunchOptions,
|
||||||
|
...this._defaultContextOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
const contextParams = await (0, _browserContext.prepareBrowserContextParams)(options);
|
||||||
|
const persistentParams = {
|
||||||
|
...contextParams,
|
||||||
|
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
||||||
|
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||||
|
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined,
|
||||||
|
channel: options.channel,
|
||||||
|
userDataDir
|
||||||
|
};
|
||||||
|
return await this._wrapApiCall(async () => {
|
||||||
|
const result = await this._channel.launchPersistentContext(persistentParams);
|
||||||
|
const context = _browserContext.BrowserContext.from(result.context);
|
||||||
|
await this._didCreateContext(context, contextParams, options, logger);
|
||||||
|
return context;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async connect(optionsOrWsEndpoint, options) {
|
||||||
|
if (typeof optionsOrWsEndpoint === 'string') return this._connect({
|
||||||
|
...options,
|
||||||
|
wsEndpoint: optionsOrWsEndpoint
|
||||||
|
});
|
||||||
|
(0, _utils.assert)(optionsOrWsEndpoint.wsEndpoint, 'options.wsEndpoint is required');
|
||||||
|
return this._connect(optionsOrWsEndpoint);
|
||||||
|
}
|
||||||
|
async _connect(params) {
|
||||||
|
const logger = params.logger;
|
||||||
|
return await this._wrapApiCall(async () => {
|
||||||
|
var _params$exposeNetwork;
|
||||||
|
const deadline = params.timeout ? (0, _utils.monotonicTime)() + params.timeout : 0;
|
||||||
|
const headers = {
|
||||||
|
'x-playwright-browser': this.name(),
|
||||||
|
...params.headers
|
||||||
|
};
|
||||||
|
const localUtils = this._connection.localUtils();
|
||||||
|
const connectParams = {
|
||||||
|
wsEndpoint: params.wsEndpoint,
|
||||||
|
headers,
|
||||||
|
exposeNetwork: (_params$exposeNetwork = params.exposeNetwork) !== null && _params$exposeNetwork !== void 0 ? _params$exposeNetwork : params._exposeNetwork,
|
||||||
|
slowMo: params.slowMo,
|
||||||
|
timeout: params.timeout
|
||||||
|
};
|
||||||
|
if (params.__testHookRedirectPortForwarding) connectParams.socksProxyRedirectPortForTest = params.__testHookRedirectPortForwarding;
|
||||||
|
const {
|
||||||
|
pipe,
|
||||||
|
headers: connectHeaders
|
||||||
|
} = await localUtils._channel.connect(connectParams);
|
||||||
|
const closePipe = () => pipe.close().catch(() => {});
|
||||||
|
const connection = new _connection.Connection(localUtils, this._instrumentation);
|
||||||
|
connection.markAsRemote();
|
||||||
|
connection.on('close', closePipe);
|
||||||
|
let browser;
|
||||||
|
let closeError;
|
||||||
|
const onPipeClosed = () => {
|
||||||
|
var _browser2;
|
||||||
|
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||||
|
for (const context of ((_browser = browser) === null || _browser === void 0 ? void 0 : _browser.contexts()) || []) {
|
||||||
|
var _browser;
|
||||||
|
for (const page of context.pages()) page._onClose();
|
||||||
|
context._onClose();
|
||||||
|
}
|
||||||
|
(_browser2 = browser) === null || _browser2 === void 0 ? void 0 : _browser2._didClose();
|
||||||
|
connection.close(closeError || _errors.kBrowserClosedError);
|
||||||
|
};
|
||||||
|
pipe.on('closed', onPipeClosed);
|
||||||
|
connection.onmessage = message => pipe.send({
|
||||||
|
message
|
||||||
|
}).catch(onPipeClosed);
|
||||||
|
pipe.on('message', ({
|
||||||
|
message
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
connection.dispatch(message);
|
||||||
|
} catch (e) {
|
||||||
|
closeError = e.toString();
|
||||||
|
closePipe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const result = await (0, _timeoutRunner.raceAgainstDeadline)(async () => {
|
||||||
|
// For tests.
|
||||||
|
if (params.__testHookBeforeCreateBrowser) await params.__testHookBeforeCreateBrowser();
|
||||||
|
const playwright = await connection.initializePlaywright();
|
||||||
|
if (!playwright._initializer.preLaunchedBrowser) {
|
||||||
|
closePipe();
|
||||||
|
throw new Error('Malformed endpoint. Did you use BrowserType.launchServer method?');
|
||||||
|
}
|
||||||
|
playwright._setSelectors(this._playwright.selectors);
|
||||||
|
browser = _browser3.Browser.from(playwright._initializer.preLaunchedBrowser);
|
||||||
|
this._didLaunchBrowser(browser, {}, logger);
|
||||||
|
browser._shouldCloseConnectionOnClose = true;
|
||||||
|
browser._connectHeaders = connectHeaders;
|
||||||
|
browser.on(_events.Events.Browser.Disconnected, closePipe);
|
||||||
|
return browser;
|
||||||
|
}, deadline);
|
||||||
|
if (!result.timedOut) {
|
||||||
|
return result.result;
|
||||||
|
} else {
|
||||||
|
closePipe();
|
||||||
|
throw new Error(`Timeout ${params.timeout}ms exceeded`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
connectOverCDP(endpointURLOrOptions, options) {
|
||||||
|
if (typeof endpointURLOrOptions === 'string') return this._connectOverCDP(endpointURLOrOptions, options);
|
||||||
|
const endpointURL = 'endpointURL' in endpointURLOrOptions ? endpointURLOrOptions.endpointURL : endpointURLOrOptions.wsEndpoint;
|
||||||
|
(0, _utils.assert)(endpointURL, 'Cannot connect over CDP without wsEndpoint.');
|
||||||
|
return this.connectOverCDP(endpointURL, endpointURLOrOptions);
|
||||||
|
}
|
||||||
|
async _connectOverCDP(endpointURL, params = {}) {
|
||||||
|
if (this.name() !== 'chromium') throw new Error('Connecting over CDP is only supported in Chromium.');
|
||||||
|
const headers = params.headers ? (0, _utils.headersObjectToArray)(params.headers) : undefined;
|
||||||
|
const result = await this._channel.connectOverCDP({
|
||||||
|
endpointURL,
|
||||||
|
headers,
|
||||||
|
slowMo: params.slowMo,
|
||||||
|
timeout: params.timeout
|
||||||
|
});
|
||||||
|
const browser = _browser3.Browser.from(result.browser);
|
||||||
|
this._didLaunchBrowser(browser, {}, params.logger);
|
||||||
|
if (result.defaultContext) await this._didCreateContext(_browserContext.BrowserContext.from(result.defaultContext), {}, {}, undefined);
|
||||||
|
return browser;
|
||||||
|
}
|
||||||
|
_didLaunchBrowser(browser, browserOptions, logger) {
|
||||||
|
browser._browserType = this;
|
||||||
|
browser._options = browserOptions;
|
||||||
|
browser._logger = logger;
|
||||||
|
}
|
||||||
|
async _didCreateContext(context, contextOptions, browserOptions, logger) {
|
||||||
|
context._logger = logger;
|
||||||
|
context._browserType = this;
|
||||||
|
this._contexts.add(context);
|
||||||
|
context._setOptions(contextOptions, browserOptions);
|
||||||
|
if (this._defaultContextTimeout !== undefined) context.setDefaultTimeout(this._defaultContextTimeout);
|
||||||
|
if (this._defaultContextNavigationTimeout !== undefined) context.setDefaultNavigationTimeout(this._defaultContextNavigationTimeout);
|
||||||
|
await this._instrumentation.onDidCreateBrowserContext(context);
|
||||||
|
}
|
||||||
|
async _willCloseContext(context) {
|
||||||
|
this._contexts.delete(context);
|
||||||
|
await this._instrumentation.onWillCloseBrowserContext(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.BrowserType = BrowserType;
|
@ -0,0 +1,53 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.CDPSession = void 0;
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CDPSession extends _channelOwner.ChannelOwner {
|
||||||
|
static from(cdpSession) {
|
||||||
|
return cdpSession._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._channel.on('event', ({
|
||||||
|
method,
|
||||||
|
params
|
||||||
|
}) => {
|
||||||
|
this.emit(method, params);
|
||||||
|
});
|
||||||
|
this.on = super.on;
|
||||||
|
this.addListener = super.addListener;
|
||||||
|
this.off = super.removeListener;
|
||||||
|
this.removeListener = super.removeListener;
|
||||||
|
this.once = super.once;
|
||||||
|
}
|
||||||
|
async send(method, params) {
|
||||||
|
const result = await this._channel.send({
|
||||||
|
method,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
return result.result;
|
||||||
|
}
|
||||||
|
async detach() {
|
||||||
|
return this._channel.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.CDPSession = CDPSession;
|
@ -0,0 +1,223 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.ChannelOwner = void 0;
|
||||||
|
var _events = require("events");
|
||||||
|
var _validator = require("../protocol/validator");
|
||||||
|
var _debugLogger = require("../common/debugLogger");
|
||||||
|
var _stackTrace = require("../utils/stackTrace");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _zones = require("../utils/zones");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ChannelOwner extends _events.EventEmitter {
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super();
|
||||||
|
this._connection = void 0;
|
||||||
|
this._parent = void 0;
|
||||||
|
this._objects = new Map();
|
||||||
|
this._type = void 0;
|
||||||
|
this._guid = void 0;
|
||||||
|
this._channel = void 0;
|
||||||
|
this._initializer = void 0;
|
||||||
|
this._logger = void 0;
|
||||||
|
this._instrumentation = void 0;
|
||||||
|
this._eventToSubscriptionMapping = new Map();
|
||||||
|
this.setMaxListeners(0);
|
||||||
|
this._connection = parent instanceof ChannelOwner ? parent._connection : parent;
|
||||||
|
this._type = type;
|
||||||
|
this._guid = guid;
|
||||||
|
this._parent = parent instanceof ChannelOwner ? parent : undefined;
|
||||||
|
this._instrumentation = this._connection._instrumentation;
|
||||||
|
this._connection._objects.set(guid, this);
|
||||||
|
if (this._parent) {
|
||||||
|
this._parent._objects.set(guid, this);
|
||||||
|
this._logger = this._parent._logger;
|
||||||
|
}
|
||||||
|
this._channel = this._createChannel(new _events.EventEmitter());
|
||||||
|
this._initializer = initializer;
|
||||||
|
}
|
||||||
|
_setEventToSubscriptionMapping(mapping) {
|
||||||
|
this._eventToSubscriptionMapping = mapping;
|
||||||
|
}
|
||||||
|
_updateSubscription(event, enabled) {
|
||||||
|
const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
|
||||||
|
if (protocolEvent) {
|
||||||
|
this._wrapApiCall(async () => {
|
||||||
|
await this._channel.updateSubscription({
|
||||||
|
event: protocolEvent,
|
||||||
|
enabled
|
||||||
|
});
|
||||||
|
}, true).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
on(event, listener) {
|
||||||
|
if (!this.listenerCount(event)) this._updateSubscription(event, true);
|
||||||
|
super.on(event, listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
addListener(event, listener) {
|
||||||
|
if (!this.listenerCount(event)) this._updateSubscription(event, true);
|
||||||
|
super.addListener(event, listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
prependListener(event, listener) {
|
||||||
|
if (!this.listenerCount(event)) this._updateSubscription(event, true);
|
||||||
|
super.prependListener(event, listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
off(event, listener) {
|
||||||
|
super.off(event, listener);
|
||||||
|
if (!this.listenerCount(event)) this._updateSubscription(event, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
removeListener(event, listener) {
|
||||||
|
super.removeListener(event, listener);
|
||||||
|
if (!this.listenerCount(event)) this._updateSubscription(event, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
_adopt(child) {
|
||||||
|
child._parent._objects.delete(child._guid);
|
||||||
|
this._objects.set(child._guid, child);
|
||||||
|
child._parent = this;
|
||||||
|
}
|
||||||
|
_dispose() {
|
||||||
|
// Clean up from parent and connection.
|
||||||
|
if (this._parent) this._parent._objects.delete(this._guid);
|
||||||
|
this._connection._objects.delete(this._guid);
|
||||||
|
|
||||||
|
// Dispose all children.
|
||||||
|
for (const object of [...this._objects.values()]) object._dispose();
|
||||||
|
this._objects.clear();
|
||||||
|
}
|
||||||
|
_debugScopeState() {
|
||||||
|
return {
|
||||||
|
_guid: this._guid,
|
||||||
|
objects: Array.from(this._objects.values()).map(o => o._debugScopeState())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_createChannel(base) {
|
||||||
|
const channel = new Proxy(base, {
|
||||||
|
get: (obj, prop) => {
|
||||||
|
if (typeof prop === 'string') {
|
||||||
|
const validator = (0, _validator.maybeFindValidator)(this._type, prop, 'Params');
|
||||||
|
if (validator) {
|
||||||
|
return params => {
|
||||||
|
return this._wrapApiCall(apiZone => {
|
||||||
|
const {
|
||||||
|
stackTrace,
|
||||||
|
csi,
|
||||||
|
callCookie,
|
||||||
|
wallTime
|
||||||
|
} = apiZone.reported ? {
|
||||||
|
csi: undefined,
|
||||||
|
callCookie: undefined,
|
||||||
|
stackTrace: null,
|
||||||
|
wallTime: undefined
|
||||||
|
} : apiZone;
|
||||||
|
apiZone.reported = true;
|
||||||
|
if (csi && stackTrace && stackTrace.apiName) csi.onApiCallBegin(stackTrace.apiName, params, stackTrace, wallTime, callCookie);
|
||||||
|
return this._connection.sendMessageToServer(this, this._type, prop, validator(params, '', {
|
||||||
|
tChannelImpl: tChannelImplToWire,
|
||||||
|
binary: this._connection.isRemote() ? 'toBase64' : 'buffer'
|
||||||
|
}), stackTrace, wallTime);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
channel._object = this;
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
async _wrapApiCall(func, isInternal = false) {
|
||||||
|
const logger = this._logger;
|
||||||
|
const stack = (0, _stackTrace.captureRawStack)();
|
||||||
|
const apiZone = _zones.zones.zoneData('apiZone', stack);
|
||||||
|
if (apiZone) return func(apiZone);
|
||||||
|
const stackTrace = (0, _stackTrace.captureLibraryStackTrace)(stack);
|
||||||
|
isInternal = isInternal || this._type === 'LocalUtils';
|
||||||
|
if (isInternal) delete stackTrace.apiName;
|
||||||
|
|
||||||
|
// Enclosing zone could have provided the apiName and wallTime.
|
||||||
|
const expectZone = _zones.zones.zoneData('expectZone', stack);
|
||||||
|
const wallTime = expectZone ? expectZone.wallTime : Date.now();
|
||||||
|
if (!isInternal && expectZone) stackTrace.apiName = expectZone.title;
|
||||||
|
const csi = isInternal ? undefined : this._instrumentation;
|
||||||
|
const callCookie = {};
|
||||||
|
const {
|
||||||
|
apiName,
|
||||||
|
frameTexts
|
||||||
|
} = stackTrace;
|
||||||
|
try {
|
||||||
|
logApiCall(logger, `=> ${apiName} started`, isInternal);
|
||||||
|
const apiZone = {
|
||||||
|
stackTrace,
|
||||||
|
isInternal,
|
||||||
|
reported: false,
|
||||||
|
csi,
|
||||||
|
callCookie,
|
||||||
|
wallTime
|
||||||
|
};
|
||||||
|
const result = await _zones.zones.run('apiZone', apiZone, async () => {
|
||||||
|
return await func(apiZone);
|
||||||
|
});
|
||||||
|
csi === null || csi === void 0 ? void 0 : csi.onApiCallEnd(callCookie);
|
||||||
|
logApiCall(logger, `<= ${apiName} succeeded`, isInternal);
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
const innerError = (process.env.PWDEBUGIMPL || (0, _utils.isUnderTest)()) && e.stack ? '\n<inner error>\n' + e.stack : '';
|
||||||
|
if (apiName && !apiName.includes('<anonymous>')) e.message = apiName + ': ' + e.message;
|
||||||
|
const stackFrames = '\n' + frameTexts.join('\n') + innerError;
|
||||||
|
if (stackFrames.trim()) e.stack = e.message + stackFrames;else e.stack = '';
|
||||||
|
csi === null || csi === void 0 ? void 0 : csi.onApiCallEnd(callCookie, e);
|
||||||
|
logApiCall(logger, `<= ${apiName} failed`, isInternal);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_toImpl() {
|
||||||
|
var _this$_connection$toI, _this$_connection;
|
||||||
|
return (_this$_connection$toI = (_this$_connection = this._connection).toImpl) === null || _this$_connection$toI === void 0 ? void 0 : _this$_connection$toI.call(_this$_connection, this);
|
||||||
|
}
|
||||||
|
toJSON() {
|
||||||
|
// Jest's expect library tries to print objects sometimes.
|
||||||
|
// RPC objects can contain links to lots of other objects,
|
||||||
|
// which can cause jest to crash. Let's help it out
|
||||||
|
// by just returning the important values.
|
||||||
|
return {
|
||||||
|
_type: this._type,
|
||||||
|
_guid: this._guid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ChannelOwner = ChannelOwner;
|
||||||
|
function logApiCall(logger, message, isNested) {
|
||||||
|
if (isNested) return;
|
||||||
|
if (logger && logger.isEnabled('api', 'info')) logger.log('api', 'info', message, [], {
|
||||||
|
color: 'cyan'
|
||||||
|
});
|
||||||
|
_debugLogger.debugLogger.log('api', message);
|
||||||
|
}
|
||||||
|
function tChannelImplToWire(names, arg, path, context) {
|
||||||
|
if (arg._object instanceof ChannelOwner && (names === '*' || names.includes(arg._object._type))) return {
|
||||||
|
guid: arg._object._guid
|
||||||
|
};
|
||||||
|
throw new _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.envObjectToArray = envObjectToArray;
|
||||||
|
exports.evaluationScript = evaluationScript;
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _utils = require("../utils");
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function envObjectToArray(env) {
|
||||||
|
const result = [];
|
||||||
|
for (const name in env) {
|
||||||
|
if (!Object.is(env[name], undefined)) result.push({
|
||||||
|
name,
|
||||||
|
value: String(env[name])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
async function evaluationScript(fun, arg, addSourceUrl = true) {
|
||||||
|
if (typeof fun === 'function') {
|
||||||
|
const source = fun.toString();
|
||||||
|
const argString = Object.is(arg, undefined) ? 'undefined' : JSON.stringify(arg);
|
||||||
|
return `(${source})(${argString})`;
|
||||||
|
}
|
||||||
|
if (arg !== undefined) throw new Error('Cannot evaluate a string with arguments');
|
||||||
|
if ((0, _utils.isString)(fun)) return fun;
|
||||||
|
if (fun.content !== undefined) return fun.content;
|
||||||
|
if (fun.path !== undefined) {
|
||||||
|
let source = await _fs.default.promises.readFile(fun.path, 'utf8');
|
||||||
|
if (addSourceUrl) source += '\n//# sourceURL=' + fun.path.replace(/\n/g, '');
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
throw new Error('Either path or content property must be present');
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.createInstrumentation = createInstrumentation;
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function createInstrumentation() {
|
||||||
|
const listeners = [];
|
||||||
|
return new Proxy({}, {
|
||||||
|
get: (obj, prop) => {
|
||||||
|
if (typeof prop !== 'string') return obj[prop];
|
||||||
|
if (prop === 'addListener') return listener => listeners.push(listener);
|
||||||
|
if (prop === 'removeListener') return listener => listeners.splice(listeners.indexOf(listener), 1);
|
||||||
|
if (prop === 'removeAllListeners') return () => listeners.splice(0, listeners.length);
|
||||||
|
if (!prop.startsWith('on')) return obj[prop];
|
||||||
|
return async (...params) => {
|
||||||
|
for (const listener of listeners) {
|
||||||
|
var _prop, _ref;
|
||||||
|
await ((_prop = (_ref = listener)[prop]) === null || _prop === void 0 ? void 0 : _prop.call(_ref, ...params));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,330 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Connection = void 0;
|
||||||
|
var _browser = require("./browser");
|
||||||
|
var _browserContext = require("./browserContext");
|
||||||
|
var _browserType = require("./browserType");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _elementHandle = require("./elementHandle");
|
||||||
|
var _frame = require("./frame");
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _network = require("./network");
|
||||||
|
var _page = require("./page");
|
||||||
|
var _worker = require("./worker");
|
||||||
|
var _consoleMessage = require("./consoleMessage");
|
||||||
|
var _dialog = require("./dialog");
|
||||||
|
var _serializers = require("../protocol/serializers");
|
||||||
|
var _cdpSession = require("./cdpSession");
|
||||||
|
var _playwright = require("./playwright");
|
||||||
|
var _electron = require("./electron");
|
||||||
|
var _stream = require("./stream");
|
||||||
|
var _writableStream = require("./writableStream");
|
||||||
|
var _debugLogger = require("../common/debugLogger");
|
||||||
|
var _selectors = require("./selectors");
|
||||||
|
var _android = require("./android");
|
||||||
|
var _stackTrace = require("../utils/stackTrace");
|
||||||
|
var _artifact = require("./artifact");
|
||||||
|
var _events = require("events");
|
||||||
|
var _jsonPipe = require("./jsonPipe");
|
||||||
|
var _fetch = require("./fetch");
|
||||||
|
var _localUtils = require("./localUtils");
|
||||||
|
var _tracing = require("./tracing");
|
||||||
|
var _validator = require("../protocol/validator");
|
||||||
|
var _clientInstrumentation = require("./clientInstrumentation");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Root extends _channelOwner.ChannelOwner {
|
||||||
|
constructor(connection) {
|
||||||
|
super(connection, 'Root', '', {});
|
||||||
|
}
|
||||||
|
async initialize() {
|
||||||
|
return _playwright.Playwright.from((await this._channel.initialize({
|
||||||
|
sdkLanguage: 'javascript'
|
||||||
|
})).playwright);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class DummyChannelOwner extends _channelOwner.ChannelOwner {}
|
||||||
|
class Connection extends _events.EventEmitter {
|
||||||
|
// Some connections allow resolving in-process dispatchers.
|
||||||
|
|
||||||
|
constructor(localUtils, instrumentation) {
|
||||||
|
super();
|
||||||
|
this._objects = new Map();
|
||||||
|
this.onmessage = message => {};
|
||||||
|
this._lastId = 0;
|
||||||
|
this._callbacks = new Map();
|
||||||
|
this._rootObject = void 0;
|
||||||
|
this._closedErrorMessage = void 0;
|
||||||
|
this._isRemote = false;
|
||||||
|
this._localUtils = void 0;
|
||||||
|
this.toImpl = void 0;
|
||||||
|
this._tracingCount = 0;
|
||||||
|
this._instrumentation = void 0;
|
||||||
|
this._rootObject = new Root(this);
|
||||||
|
this._localUtils = localUtils;
|
||||||
|
this._instrumentation = instrumentation || (0, _clientInstrumentation.createInstrumentation)();
|
||||||
|
}
|
||||||
|
markAsRemote() {
|
||||||
|
this._isRemote = true;
|
||||||
|
}
|
||||||
|
isRemote() {
|
||||||
|
return this._isRemote;
|
||||||
|
}
|
||||||
|
localUtils() {
|
||||||
|
return this._localUtils;
|
||||||
|
}
|
||||||
|
async initializePlaywright() {
|
||||||
|
return await this._rootObject.initialize();
|
||||||
|
}
|
||||||
|
pendingProtocolCalls() {
|
||||||
|
return Array.from(this._callbacks.values()).map(callback => callback.stackTrace).filter(Boolean);
|
||||||
|
}
|
||||||
|
getObjectWithKnownName(guid) {
|
||||||
|
return this._objects.get(guid);
|
||||||
|
}
|
||||||
|
async setIsTracing(isTracing) {
|
||||||
|
if (isTracing) this._tracingCount++;else this._tracingCount--;
|
||||||
|
}
|
||||||
|
async sendMessageToServer(object, type, method, params, stackTrace, wallTime) {
|
||||||
|
var _this$_localUtils;
|
||||||
|
if (this._closedErrorMessage) throw new Error(this._closedErrorMessage);
|
||||||
|
const {
|
||||||
|
apiName,
|
||||||
|
frames
|
||||||
|
} = stackTrace || {
|
||||||
|
apiName: '',
|
||||||
|
frames: []
|
||||||
|
};
|
||||||
|
const guid = object._guid;
|
||||||
|
const id = ++this._lastId;
|
||||||
|
const converted = {
|
||||||
|
id,
|
||||||
|
guid,
|
||||||
|
method,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
// Do not include metadata in debug logs to avoid noise.
|
||||||
|
_debugLogger.debugLogger.log('channel:command', converted);
|
||||||
|
const location = frames[0] ? {
|
||||||
|
file: frames[0].file,
|
||||||
|
line: frames[0].line,
|
||||||
|
column: frames[0].column
|
||||||
|
} : undefined;
|
||||||
|
const metadata = {
|
||||||
|
wallTime,
|
||||||
|
apiName,
|
||||||
|
location,
|
||||||
|
internal: !apiName
|
||||||
|
};
|
||||||
|
this.onmessage({
|
||||||
|
...converted,
|
||||||
|
metadata
|
||||||
|
});
|
||||||
|
if (this._tracingCount && frames && type !== 'LocalUtils') (_this$_localUtils = this._localUtils) === null || _this$_localUtils === void 0 ? void 0 : _this$_localUtils._channel.addStackToTracingNoReply({
|
||||||
|
callData: {
|
||||||
|
stack: frames,
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
return await new Promise((resolve, reject) => this._callbacks.set(id, {
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
stackTrace,
|
||||||
|
type,
|
||||||
|
method
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
dispatch(message) {
|
||||||
|
if (this._closedErrorMessage) return;
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
guid,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
result,
|
||||||
|
error
|
||||||
|
} = message;
|
||||||
|
if (id) {
|
||||||
|
_debugLogger.debugLogger.log('channel:response', message);
|
||||||
|
const callback = this._callbacks.get(id);
|
||||||
|
if (!callback) throw new Error(`Cannot find command to respond: ${id}`);
|
||||||
|
this._callbacks.delete(id);
|
||||||
|
if (error && !result) {
|
||||||
|
callback.reject((0, _serializers.parseError)(error));
|
||||||
|
} else {
|
||||||
|
const validator = (0, _validator.findValidator)(callback.type, callback.method, 'Result');
|
||||||
|
callback.resolve(validator(result, '', {
|
||||||
|
tChannelImpl: this._tChannelImplFromWire.bind(this),
|
||||||
|
binary: this.isRemote() ? 'fromBase64' : 'buffer'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_debugLogger.debugLogger.log('channel:event', message);
|
||||||
|
if (method === '__create__') {
|
||||||
|
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const object = this._objects.get(guid);
|
||||||
|
if (!object) throw new Error(`Cannot find object to "${method}": ${guid}`);
|
||||||
|
if (method === '__adopt__') {
|
||||||
|
const child = this._objects.get(params.guid);
|
||||||
|
if (!child) throw new Error(`Unknown new child: ${params.guid}`);
|
||||||
|
object._adopt(child);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (method === '__dispose__') {
|
||||||
|
object._dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const validator = (0, _validator.findValidator)(object._type, method, 'Event');
|
||||||
|
object._channel.emit(method, validator(params, '', {
|
||||||
|
tChannelImpl: this._tChannelImplFromWire.bind(this),
|
||||||
|
binary: this.isRemote() ? 'fromBase64' : 'buffer'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
close(errorMessage = 'Connection closed') {
|
||||||
|
const stack = (0, _stackTrace.captureLibraryStackTrace)().frameTexts.join('\n');
|
||||||
|
if (stack) errorMessage += '\n ==== Closed by ====\n' + stack + '\n';
|
||||||
|
this._closedErrorMessage = errorMessage;
|
||||||
|
for (const callback of this._callbacks.values()) callback.reject(new Error(errorMessage));
|
||||||
|
this._callbacks.clear();
|
||||||
|
this.emit('close');
|
||||||
|
}
|
||||||
|
_tChannelImplFromWire(names, arg, path, context) {
|
||||||
|
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
|
||||||
|
const object = this._objects.get(arg.guid);
|
||||||
|
if (!object) throw new Error(`Object with guid ${arg.guid} was not bound in the connection`);
|
||||||
|
if (names !== '*' && !names.includes(object._type)) throw new _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
|
||||||
|
return object._channel;
|
||||||
|
}
|
||||||
|
throw new _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
|
||||||
|
}
|
||||||
|
_createRemoteObject(parentGuid, type, guid, initializer) {
|
||||||
|
const parent = this._objects.get(parentGuid);
|
||||||
|
if (!parent) throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
|
||||||
|
let result;
|
||||||
|
const validator = (0, _validator.findValidator)(type, '', 'Initializer');
|
||||||
|
initializer = validator(initializer, '', {
|
||||||
|
tChannelImpl: this._tChannelImplFromWire.bind(this),
|
||||||
|
binary: this.isRemote() ? 'fromBase64' : 'buffer'
|
||||||
|
});
|
||||||
|
switch (type) {
|
||||||
|
case 'Android':
|
||||||
|
result = new _android.Android(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'AndroidSocket':
|
||||||
|
result = new _android.AndroidSocket(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'AndroidDevice':
|
||||||
|
result = new _android.AndroidDevice(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'APIRequestContext':
|
||||||
|
result = new _fetch.APIRequestContext(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Artifact':
|
||||||
|
result = new _artifact.Artifact(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'BindingCall':
|
||||||
|
result = new _page.BindingCall(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Browser':
|
||||||
|
result = new _browser.Browser(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'BrowserContext':
|
||||||
|
result = new _browserContext.BrowserContext(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'BrowserType':
|
||||||
|
result = new _browserType.BrowserType(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'CDPSession':
|
||||||
|
result = new _cdpSession.CDPSession(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'ConsoleMessage':
|
||||||
|
result = new _consoleMessage.ConsoleMessage(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Dialog':
|
||||||
|
result = new _dialog.Dialog(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Electron':
|
||||||
|
result = new _electron.Electron(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'ElectronApplication':
|
||||||
|
result = new _electron.ElectronApplication(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'ElementHandle':
|
||||||
|
result = new _elementHandle.ElementHandle(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Frame':
|
||||||
|
result = new _frame.Frame(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'JSHandle':
|
||||||
|
result = new _jsHandle.JSHandle(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'JsonPipe':
|
||||||
|
result = new _jsonPipe.JsonPipe(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'LocalUtils':
|
||||||
|
result = new _localUtils.LocalUtils(parent, type, guid, initializer);
|
||||||
|
if (!this._localUtils) this._localUtils = result;
|
||||||
|
break;
|
||||||
|
case 'Page':
|
||||||
|
result = new _page.Page(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Playwright':
|
||||||
|
result = new _playwright.Playwright(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Request':
|
||||||
|
result = new _network.Request(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Response':
|
||||||
|
result = new _network.Response(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Route':
|
||||||
|
result = new _network.Route(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Stream':
|
||||||
|
result = new _stream.Stream(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Selectors':
|
||||||
|
result = new _selectors.SelectorsOwner(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'SocksSupport':
|
||||||
|
result = new DummyChannelOwner(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Tracing':
|
||||||
|
result = new _tracing.Tracing(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'WebSocket':
|
||||||
|
result = new _network.WebSocket(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'Worker':
|
||||||
|
result = new _worker.Worker(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
case 'WritableStream':
|
||||||
|
result = new _writableStream.WritableStream(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Missing type ' + type);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Connection = Connection;
|
@ -0,0 +1,61 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.ConsoleMessage = void 0;
|
||||||
|
var util = _interopRequireWildcard(require("util"));
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _page = require("./page");
|
||||||
|
let _util$inspect$custom;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
_util$inspect$custom = util.inspect.custom;
|
||||||
|
class ConsoleMessage extends _channelOwner.ChannelOwner {
|
||||||
|
static from(message) {
|
||||||
|
return message._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
// Note: currently, we only report console messages for pages and they always have a page.
|
||||||
|
// However, in the future we might report console messages for service workers or something else,
|
||||||
|
// where page() would be null.
|
||||||
|
this._page = void 0;
|
||||||
|
this._page = _page.Page.fromNullable(initializer.page);
|
||||||
|
}
|
||||||
|
page() {
|
||||||
|
return this._page;
|
||||||
|
}
|
||||||
|
type() {
|
||||||
|
return this._initializer.type;
|
||||||
|
}
|
||||||
|
text() {
|
||||||
|
return this._initializer.text;
|
||||||
|
}
|
||||||
|
args() {
|
||||||
|
return this._initializer.args.map(_jsHandle.JSHandle.from);
|
||||||
|
}
|
||||||
|
location() {
|
||||||
|
return this._initializer.location;
|
||||||
|
}
|
||||||
|
[_util$inspect$custom]() {
|
||||||
|
return this.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ConsoleMessage = ConsoleMessage;
|
@ -0,0 +1,41 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Coverage = void 0;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Coverage {
|
||||||
|
constructor(channel) {
|
||||||
|
this._channel = void 0;
|
||||||
|
this._channel = channel;
|
||||||
|
}
|
||||||
|
async startJSCoverage(options = {}) {
|
||||||
|
await this._channel.startJSCoverage(options);
|
||||||
|
}
|
||||||
|
async stopJSCoverage() {
|
||||||
|
return (await this._channel.stopJSCoverage()).entries;
|
||||||
|
}
|
||||||
|
async startCSSCoverage(options = {}) {
|
||||||
|
await this._channel.startCSSCoverage(options);
|
||||||
|
}
|
||||||
|
async stopCSSCoverage() {
|
||||||
|
return (await this._channel.stopCSSCoverage()).entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Coverage = Coverage;
|
@ -0,0 +1,57 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Dialog = void 0;
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _page = require("./page");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Dialog extends _channelOwner.ChannelOwner {
|
||||||
|
static from(dialog) {
|
||||||
|
return dialog._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
// Note: dialogs that open early during page initialization block it.
|
||||||
|
// Therefore, we must report the dialog without a page to be able to handle it.
|
||||||
|
this._page = void 0;
|
||||||
|
this._page = _page.Page.fromNullable(initializer.page);
|
||||||
|
}
|
||||||
|
page() {
|
||||||
|
return this._page;
|
||||||
|
}
|
||||||
|
type() {
|
||||||
|
return this._initializer.type;
|
||||||
|
}
|
||||||
|
message() {
|
||||||
|
return this._initializer.message;
|
||||||
|
}
|
||||||
|
defaultValue() {
|
||||||
|
return this._initializer.defaultValue;
|
||||||
|
}
|
||||||
|
async accept(promptText) {
|
||||||
|
await this._channel.accept({
|
||||||
|
promptText
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async dismiss() {
|
||||||
|
await this._channel.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Dialog = Dialog;
|
@ -0,0 +1,62 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Download = void 0;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Download {
|
||||||
|
constructor(page, url, suggestedFilename, artifact) {
|
||||||
|
this._page = void 0;
|
||||||
|
this._url = void 0;
|
||||||
|
this._suggestedFilename = void 0;
|
||||||
|
this._artifact = void 0;
|
||||||
|
this._page = page;
|
||||||
|
this._url = url;
|
||||||
|
this._suggestedFilename = suggestedFilename;
|
||||||
|
this._artifact = artifact;
|
||||||
|
}
|
||||||
|
page() {
|
||||||
|
return this._page;
|
||||||
|
}
|
||||||
|
url() {
|
||||||
|
return this._url;
|
||||||
|
}
|
||||||
|
suggestedFilename() {
|
||||||
|
return this._suggestedFilename;
|
||||||
|
}
|
||||||
|
async path() {
|
||||||
|
return this._artifact.pathAfterFinished();
|
||||||
|
}
|
||||||
|
async saveAs(path) {
|
||||||
|
return this._artifact.saveAs(path);
|
||||||
|
}
|
||||||
|
async failure() {
|
||||||
|
return this._artifact.failure();
|
||||||
|
}
|
||||||
|
async createReadStream() {
|
||||||
|
return this._artifact.createReadStream();
|
||||||
|
}
|
||||||
|
async cancel() {
|
||||||
|
return this._artifact.cancel();
|
||||||
|
}
|
||||||
|
async delete() {
|
||||||
|
return this._artifact.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Download = Download;
|
@ -0,0 +1,125 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.ElectronApplication = exports.Electron = void 0;
|
||||||
|
var _timeoutSettings = require("../common/timeoutSettings");
|
||||||
|
var _browserContext = require("./browserContext");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _clientHelper = require("./clientHelper");
|
||||||
|
var _events = require("./events");
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _waiter = require("./waiter");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Electron extends _channelOwner.ChannelOwner {
|
||||||
|
static from(electron) {
|
||||||
|
return electron._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
}
|
||||||
|
async launch(options = {}) {
|
||||||
|
const params = {
|
||||||
|
...(await (0, _browserContext.prepareBrowserContextParams)(options)),
|
||||||
|
env: (0, _clientHelper.envObjectToArray)(options.env ? options.env : process.env),
|
||||||
|
tracesDir: options.tracesDir
|
||||||
|
};
|
||||||
|
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
|
||||||
|
app._context._options = params;
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Electron = Electron;
|
||||||
|
class ElectronApplication extends _channelOwner.ChannelOwner {
|
||||||
|
static from(electronApplication) {
|
||||||
|
return electronApplication._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._context = void 0;
|
||||||
|
this._windows = new Set();
|
||||||
|
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
|
||||||
|
this._isClosed = false;
|
||||||
|
this._context = _browserContext.BrowserContext.from(initializer.context);
|
||||||
|
for (const page of this._context._pages) this._onPage(page);
|
||||||
|
this._context.on(_events.Events.BrowserContext.Page, page => this._onPage(page));
|
||||||
|
this._channel.on('close', () => {
|
||||||
|
this._isClosed = true;
|
||||||
|
this.emit(_events.Events.ElectronApplication.Close);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process() {
|
||||||
|
return this._toImpl().process();
|
||||||
|
}
|
||||||
|
_onPage(page) {
|
||||||
|
this._windows.add(page);
|
||||||
|
this.emit(_events.Events.ElectronApplication.Window, page);
|
||||||
|
page.once(_events.Events.Page.Close, () => this._windows.delete(page));
|
||||||
|
}
|
||||||
|
windows() {
|
||||||
|
// TODO: add ElectronPage class inherting from Page.
|
||||||
|
return [...this._windows];
|
||||||
|
}
|
||||||
|
async firstWindow(options) {
|
||||||
|
if (this._windows.size) return this._windows.values().next().value;
|
||||||
|
return this.waitForEvent('window', options);
|
||||||
|
}
|
||||||
|
context() {
|
||||||
|
return this._context;
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
if (this._isClosed) return;
|
||||||
|
await this._channel.close().catch(() => {});
|
||||||
|
}
|
||||||
|
async waitForEvent(event, optionsOrPredicate = {}) {
|
||||||
|
return this._wrapApiCall(async () => {
|
||||||
|
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
|
const waiter = _waiter.Waiter.createForEvent(this, event);
|
||||||
|
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
|
||||||
|
if (event !== _events.Events.ElectronApplication.Close) waiter.rejectOnEvent(this, _events.Events.ElectronApplication.Close, new Error('Electron application closed'));
|
||||||
|
const result = await waiter.waitForEvent(this, event, predicate);
|
||||||
|
waiter.dispose();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async browserWindow(page) {
|
||||||
|
const result = await this._channel.browserWindow({
|
||||||
|
page: page._channel
|
||||||
|
});
|
||||||
|
return _jsHandle.JSHandle.from(result.handle);
|
||||||
|
}
|
||||||
|
async evaluate(pageFunction, arg) {
|
||||||
|
const result = await this._channel.evaluateExpression({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return (0, _jsHandle.parseResult)(result.value);
|
||||||
|
}
|
||||||
|
async evaluateHandle(pageFunction, arg) {
|
||||||
|
const result = await this._channel.evaluateExpressionHandle({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return _jsHandle.JSHandle.from(result.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ElectronApplication = ElectronApplication;
|
@ -0,0 +1,312 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.ElementHandle = void 0;
|
||||||
|
exports.convertInputFiles = convertInputFiles;
|
||||||
|
exports.convertSelectOptionValues = convertSelectOptionValues;
|
||||||
|
exports.determineScreenshotType = determineScreenshotType;
|
||||||
|
var _frame = require("./frame");
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _utilsBundle = require("../utilsBundle");
|
||||||
|
var _path = _interopRequireDefault(require("path"));
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _fileUtils = require("../utils/fileUtils");
|
||||||
|
var _writableStream = require("./writableStream");
|
||||||
|
var _stream = require("stream");
|
||||||
|
var _util = require("util");
|
||||||
|
var _debugLogger = require("../common/debugLogger");
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* 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 pipelineAsync = (0, _util.promisify)(_stream.pipeline);
|
||||||
|
class ElementHandle extends _jsHandle.JSHandle {
|
||||||
|
static from(handle) {
|
||||||
|
return handle._object;
|
||||||
|
}
|
||||||
|
static fromNullable(handle) {
|
||||||
|
return handle ? ElementHandle.from(handle) : null;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._elementChannel = void 0;
|
||||||
|
this._elementChannel = this._channel;
|
||||||
|
}
|
||||||
|
asElement() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
async ownerFrame() {
|
||||||
|
return _frame.Frame.fromNullable((await this._elementChannel.ownerFrame()).frame);
|
||||||
|
}
|
||||||
|
async contentFrame() {
|
||||||
|
return _frame.Frame.fromNullable((await this._elementChannel.contentFrame()).frame);
|
||||||
|
}
|
||||||
|
async getAttribute(name) {
|
||||||
|
const value = (await this._elementChannel.getAttribute({
|
||||||
|
name
|
||||||
|
})).value;
|
||||||
|
return value === undefined ? null : value;
|
||||||
|
}
|
||||||
|
async inputValue() {
|
||||||
|
return (await this._elementChannel.inputValue()).value;
|
||||||
|
}
|
||||||
|
async textContent() {
|
||||||
|
const value = (await this._elementChannel.textContent()).value;
|
||||||
|
return value === undefined ? null : value;
|
||||||
|
}
|
||||||
|
async innerText() {
|
||||||
|
return (await this._elementChannel.innerText()).value;
|
||||||
|
}
|
||||||
|
async innerHTML() {
|
||||||
|
return (await this._elementChannel.innerHTML()).value;
|
||||||
|
}
|
||||||
|
async isChecked() {
|
||||||
|
return (await this._elementChannel.isChecked()).value;
|
||||||
|
}
|
||||||
|
async isDisabled() {
|
||||||
|
return (await this._elementChannel.isDisabled()).value;
|
||||||
|
}
|
||||||
|
async isEditable() {
|
||||||
|
return (await this._elementChannel.isEditable()).value;
|
||||||
|
}
|
||||||
|
async isEnabled() {
|
||||||
|
return (await this._elementChannel.isEnabled()).value;
|
||||||
|
}
|
||||||
|
async isHidden() {
|
||||||
|
return (await this._elementChannel.isHidden()).value;
|
||||||
|
}
|
||||||
|
async isVisible() {
|
||||||
|
return (await this._elementChannel.isVisible()).value;
|
||||||
|
}
|
||||||
|
async dispatchEvent(type, eventInit = {}) {
|
||||||
|
await this._elementChannel.dispatchEvent({
|
||||||
|
type,
|
||||||
|
eventInit: (0, _jsHandle.serializeArgument)(eventInit)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async scrollIntoViewIfNeeded(options = {}) {
|
||||||
|
await this._elementChannel.scrollIntoViewIfNeeded(options);
|
||||||
|
}
|
||||||
|
async hover(options = {}) {
|
||||||
|
await this._elementChannel.hover(options);
|
||||||
|
}
|
||||||
|
async click(options = {}) {
|
||||||
|
return await this._elementChannel.click(options);
|
||||||
|
}
|
||||||
|
async dblclick(options = {}) {
|
||||||
|
return await this._elementChannel.dblclick(options);
|
||||||
|
}
|
||||||
|
async tap(options = {}) {
|
||||||
|
return await this._elementChannel.tap(options);
|
||||||
|
}
|
||||||
|
async selectOption(values, options = {}) {
|
||||||
|
const result = await this._elementChannel.selectOption({
|
||||||
|
...convertSelectOptionValues(values),
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
return result.values;
|
||||||
|
}
|
||||||
|
async fill(value, options = {}) {
|
||||||
|
return await this._elementChannel.fill({
|
||||||
|
value,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async selectText(options = {}) {
|
||||||
|
await this._elementChannel.selectText(options);
|
||||||
|
}
|
||||||
|
async setInputFiles(files, options = {}) {
|
||||||
|
const frame = await this.ownerFrame();
|
||||||
|
if (!frame) throw new Error('Cannot set input files to detached element');
|
||||||
|
const converted = await convertInputFiles(files, frame.page().context());
|
||||||
|
if (converted.files) {
|
||||||
|
await this._elementChannel.setInputFiles({
|
||||||
|
files: converted.files,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_debugLogger.debugLogger.log('api', 'switching to large files mode');
|
||||||
|
await this._elementChannel.setInputFilePaths({
|
||||||
|
...converted,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async focus() {
|
||||||
|
await this._elementChannel.focus();
|
||||||
|
}
|
||||||
|
async type(text, options = {}) {
|
||||||
|
await this._elementChannel.type({
|
||||||
|
text,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async press(key, options = {}) {
|
||||||
|
await this._elementChannel.press({
|
||||||
|
key,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async check(options = {}) {
|
||||||
|
return await this._elementChannel.check(options);
|
||||||
|
}
|
||||||
|
async uncheck(options = {}) {
|
||||||
|
return await this._elementChannel.uncheck(options);
|
||||||
|
}
|
||||||
|
async setChecked(checked, options) {
|
||||||
|
if (checked) await this.check(options);else await this.uncheck(options);
|
||||||
|
}
|
||||||
|
async boundingBox() {
|
||||||
|
const value = (await this._elementChannel.boundingBox()).value;
|
||||||
|
return value === undefined ? null : value;
|
||||||
|
}
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
const copy = {
|
||||||
|
...options,
|
||||||
|
mask: undefined
|
||||||
|
};
|
||||||
|
if (!copy.type) copy.type = determineScreenshotType(options);
|
||||||
|
if (options.mask) {
|
||||||
|
copy.mask = options.mask.map(locator => ({
|
||||||
|
frame: locator._frame._channel,
|
||||||
|
selector: locator._selector
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const result = await this._elementChannel.screenshot(copy);
|
||||||
|
if (options.path) {
|
||||||
|
await (0, _fileUtils.mkdirIfNeeded)(options.path);
|
||||||
|
await _fs.default.promises.writeFile(options.path, result.binary);
|
||||||
|
}
|
||||||
|
return result.binary;
|
||||||
|
}
|
||||||
|
async $(selector) {
|
||||||
|
return ElementHandle.fromNullable((await this._elementChannel.querySelector({
|
||||||
|
selector
|
||||||
|
})).element);
|
||||||
|
}
|
||||||
|
async $$(selector) {
|
||||||
|
const result = await this._elementChannel.querySelectorAll({
|
||||||
|
selector
|
||||||
|
});
|
||||||
|
return result.elements.map(h => ElementHandle.from(h));
|
||||||
|
}
|
||||||
|
async $eval(selector, pageFunction, arg) {
|
||||||
|
const result = await this._elementChannel.evalOnSelector({
|
||||||
|
selector,
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return (0, _jsHandle.parseResult)(result.value);
|
||||||
|
}
|
||||||
|
async $$eval(selector, pageFunction, arg) {
|
||||||
|
const result = await this._elementChannel.evalOnSelectorAll({
|
||||||
|
selector,
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return (0, _jsHandle.parseResult)(result.value);
|
||||||
|
}
|
||||||
|
async waitForElementState(state, options = {}) {
|
||||||
|
return await this._elementChannel.waitForElementState({
|
||||||
|
state,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async waitForSelector(selector, options = {}) {
|
||||||
|
const result = await this._elementChannel.waitForSelector({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
return ElementHandle.fromNullable(result.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ElementHandle = ElementHandle;
|
||||||
|
function convertSelectOptionValues(values) {
|
||||||
|
if (values === null) return {};
|
||||||
|
if (!Array.isArray(values)) values = [values];
|
||||||
|
if (!values.length) return {};
|
||||||
|
for (let i = 0; i < values.length; i++) (0, _utils.assert)(values[i] !== null, `options[${i}]: expected object, got null`);
|
||||||
|
if (values[0] instanceof ElementHandle) return {
|
||||||
|
elements: values.map(v => v._elementChannel)
|
||||||
|
};
|
||||||
|
if ((0, _utils.isString)(values[0])) return {
|
||||||
|
options: values.map(valueOrLabel => ({
|
||||||
|
valueOrLabel
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
options: values
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function convertInputFiles(files, context) {
|
||||||
|
const items = Array.isArray(files) ? files.slice() : [files];
|
||||||
|
const sizeLimit = 50 * 1024 * 1024;
|
||||||
|
const totalBufferSizeExceedsLimit = items.reduce((size, item) => size + (typeof item === 'object' && item.buffer ? item.buffer.byteLength : 0), 0) > sizeLimit;
|
||||||
|
if (totalBufferSizeExceedsLimit) throw new Error('Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.');
|
||||||
|
const stats = await Promise.all(items.filter(_utils.isString).map(item => _fs.default.promises.stat(item)));
|
||||||
|
const totalFileSizeExceedsLimit = stats.reduce((acc, stat) => acc + stat.size, 0) > sizeLimit;
|
||||||
|
if (totalFileSizeExceedsLimit) {
|
||||||
|
if (context._connection.isRemote()) {
|
||||||
|
const streams = await Promise.all(items.map(async item => {
|
||||||
|
(0, _utils.assert)((0, _utils.isString)(item));
|
||||||
|
const {
|
||||||
|
writableStream: stream
|
||||||
|
} = await context._channel.createTempFile({
|
||||||
|
name: _path.default.basename(item)
|
||||||
|
});
|
||||||
|
const writable = _writableStream.WritableStream.from(stream);
|
||||||
|
await pipelineAsync(_fs.default.createReadStream(item), writable.stream());
|
||||||
|
return stream;
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
streams
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
localPaths: items.map(f => _path.default.resolve(f))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const filePayloads = await Promise.all(items.map(async item => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
return {
|
||||||
|
name: _path.default.basename(item),
|
||||||
|
buffer: await _fs.default.promises.readFile(item)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
mimeType: item.mimeType,
|
||||||
|
buffer: item.buffer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
files: filePayloads
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function determineScreenshotType(options) {
|
||||||
|
if (options.path) {
|
||||||
|
const mimeType = _utilsBundle.mime.getType(options.path);
|
||||||
|
if (mimeType === 'image/png') return 'png';else if (mimeType === 'image/jpeg') return 'jpeg';
|
||||||
|
throw new Error(`path: unsupported mime type "${mimeType}"`);
|
||||||
|
}
|
||||||
|
return options.type;
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Events = void 0;
|
||||||
|
/**
|
||||||
|
* Copyright 2019 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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 Events = {
|
||||||
|
AndroidDevice: {
|
||||||
|
WebView: 'webview',
|
||||||
|
Close: 'close'
|
||||||
|
},
|
||||||
|
AndroidSocket: {
|
||||||
|
Data: 'data',
|
||||||
|
Close: 'close'
|
||||||
|
},
|
||||||
|
AndroidWebView: {
|
||||||
|
Close: 'close'
|
||||||
|
},
|
||||||
|
Browser: {
|
||||||
|
Disconnected: 'disconnected'
|
||||||
|
},
|
||||||
|
BrowserContext: {
|
||||||
|
Console: 'console',
|
||||||
|
Close: 'close',
|
||||||
|
Dialog: 'dialog',
|
||||||
|
Page: 'page',
|
||||||
|
BackgroundPage: 'backgroundpage',
|
||||||
|
ServiceWorker: 'serviceworker',
|
||||||
|
Request: 'request',
|
||||||
|
Response: 'response',
|
||||||
|
RequestFailed: 'requestfailed',
|
||||||
|
RequestFinished: 'requestfinished'
|
||||||
|
},
|
||||||
|
BrowserServer: {
|
||||||
|
Close: 'close'
|
||||||
|
},
|
||||||
|
Page: {
|
||||||
|
Close: 'close',
|
||||||
|
Crash: 'crash',
|
||||||
|
Console: 'console',
|
||||||
|
Dialog: 'dialog',
|
||||||
|
Download: 'download',
|
||||||
|
FileChooser: 'filechooser',
|
||||||
|
DOMContentLoaded: 'domcontentloaded',
|
||||||
|
// Can't use just 'error' due to node.js special treatment of error events.
|
||||||
|
// @see https://nodejs.org/api/events.html#events_error_events
|
||||||
|
PageError: 'pageerror',
|
||||||
|
Request: 'request',
|
||||||
|
Response: 'response',
|
||||||
|
RequestFailed: 'requestfailed',
|
||||||
|
RequestFinished: 'requestfinished',
|
||||||
|
FrameAttached: 'frameattached',
|
||||||
|
FrameDetached: 'framedetached',
|
||||||
|
FrameNavigated: 'framenavigated',
|
||||||
|
Load: 'load',
|
||||||
|
Popup: 'popup',
|
||||||
|
WebSocket: 'websocket',
|
||||||
|
Worker: 'worker'
|
||||||
|
},
|
||||||
|
WebSocket: {
|
||||||
|
Close: 'close',
|
||||||
|
Error: 'socketerror',
|
||||||
|
FrameReceived: 'framereceived',
|
||||||
|
FrameSent: 'framesent'
|
||||||
|
},
|
||||||
|
Worker: {
|
||||||
|
Close: 'close'
|
||||||
|
},
|
||||||
|
ElectronApplication: {
|
||||||
|
Close: 'close',
|
||||||
|
Window: 'window'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.Events = Events;
|
@ -0,0 +1,327 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.APIResponse = exports.APIRequestContext = exports.APIRequest = void 0;
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _path = _interopRequireDefault(require("path"));
|
||||||
|
var util = _interopRequireWildcard(require("util"));
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _fileUtils = require("../utils/fileUtils");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _network = require("./network");
|
||||||
|
var _tracing = require("./tracing");
|
||||||
|
let _util$inspect$custom;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
class APIRequest {
|
||||||
|
// Instrumentation.
|
||||||
|
|
||||||
|
constructor(playwright) {
|
||||||
|
this._playwright = void 0;
|
||||||
|
this._contexts = new Set();
|
||||||
|
this._defaultContextOptions = void 0;
|
||||||
|
this._playwright = playwright;
|
||||||
|
}
|
||||||
|
async newContext(options = {}) {
|
||||||
|
var _this$_defaultContext;
|
||||||
|
options = {
|
||||||
|
...this._defaultContextOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
const storageState = typeof options.storageState === 'string' ? JSON.parse(await _fs.default.promises.readFile(options.storageState, 'utf8')) : options.storageState;
|
||||||
|
// We do not expose tracesDir in the API, so do not allow options to accidentally override it.
|
||||||
|
const tracesDir = (_this$_defaultContext = this._defaultContextOptions) === null || _this$_defaultContext === void 0 ? void 0 : _this$_defaultContext.tracesDir;
|
||||||
|
const context = APIRequestContext.from((await this._playwright._channel.newRequest({
|
||||||
|
...options,
|
||||||
|
extraHTTPHeaders: options.extraHTTPHeaders ? (0, _utils.headersObjectToArray)(options.extraHTTPHeaders) : undefined,
|
||||||
|
storageState,
|
||||||
|
tracesDir
|
||||||
|
})).request);
|
||||||
|
this._contexts.add(context);
|
||||||
|
context._request = this;
|
||||||
|
context._tracing._tracesDir = tracesDir;
|
||||||
|
await context._instrumentation.onDidCreateRequestContext(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.APIRequest = APIRequest;
|
||||||
|
class APIRequestContext extends _channelOwner.ChannelOwner {
|
||||||
|
static from(channel) {
|
||||||
|
return channel._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._request = void 0;
|
||||||
|
this._tracing = void 0;
|
||||||
|
this._tracing = _tracing.Tracing.from(initializer.tracing);
|
||||||
|
}
|
||||||
|
async dispose() {
|
||||||
|
var _this$_request;
|
||||||
|
await this._instrumentation.onWillCloseRequestContext(this);
|
||||||
|
await this._channel.dispose();
|
||||||
|
(_this$_request = this._request) === null || _this$_request === void 0 ? void 0 : _this$_request._contexts.delete(this);
|
||||||
|
}
|
||||||
|
async delete(url, options) {
|
||||||
|
return this.fetch(url, {
|
||||||
|
...options,
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async head(url, options) {
|
||||||
|
return this.fetch(url, {
|
||||||
|
...options,
|
||||||
|
method: 'HEAD'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async get(url, options) {
|
||||||
|
return this.fetch(url, {
|
||||||
|
...options,
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async patch(url, options) {
|
||||||
|
return this.fetch(url, {
|
||||||
|
...options,
|
||||||
|
method: 'PATCH'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async post(url, options) {
|
||||||
|
return this.fetch(url, {
|
||||||
|
...options,
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async put(url, options) {
|
||||||
|
return this.fetch(url, {
|
||||||
|
...options,
|
||||||
|
method: 'PUT'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async fetch(urlOrRequest, options = {}) {
|
||||||
|
const url = (0, _utils.isString)(urlOrRequest) ? urlOrRequest : undefined;
|
||||||
|
const request = (0, _utils.isString)(urlOrRequest) ? undefined : urlOrRequest;
|
||||||
|
return this._innerFetch({
|
||||||
|
url,
|
||||||
|
request,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _innerFetch(options = {}) {
|
||||||
|
return this._wrapApiCall(async () => {
|
||||||
|
var _options$request, _options$request2, _options$request3;
|
||||||
|
(0, _utils.assert)(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');
|
||||||
|
(0, _utils.assert)((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
|
||||||
|
(0, _utils.assert)(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' should be greater than or equal to '0'`);
|
||||||
|
const url = options.url !== undefined ? options.url : options.request.url();
|
||||||
|
const params = objectToArray(options.params);
|
||||||
|
const method = options.method || ((_options$request = options.request) === null || _options$request === void 0 ? void 0 : _options$request.method());
|
||||||
|
const maxRedirects = options.maxRedirects;
|
||||||
|
// Cannot call allHeaders() here as the request may be paused inside route handler.
|
||||||
|
const headersObj = options.headers || ((_options$request2 = options.request) === null || _options$request2 === void 0 ? void 0 : _options$request2.headers());
|
||||||
|
const headers = headersObj ? (0, _utils.headersObjectToArray)(headersObj) : undefined;
|
||||||
|
let jsonData;
|
||||||
|
let formData;
|
||||||
|
let multipartData;
|
||||||
|
let postDataBuffer;
|
||||||
|
if (options.data !== undefined) {
|
||||||
|
if ((0, _utils.isString)(options.data)) {
|
||||||
|
if (isJsonContentType(headers)) jsonData = options.data;else postDataBuffer = Buffer.from(options.data, 'utf8');
|
||||||
|
} else if (Buffer.isBuffer(options.data)) {
|
||||||
|
postDataBuffer = options.data;
|
||||||
|
} else if (typeof options.data === 'object' || typeof options.data === 'number' || typeof options.data === 'boolean') {
|
||||||
|
jsonData = options.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected 'data' type`);
|
||||||
|
}
|
||||||
|
} else if (options.form) {
|
||||||
|
formData = objectToArray(options.form);
|
||||||
|
} else if (options.multipart) {
|
||||||
|
multipartData = [];
|
||||||
|
// Convert file-like values to ServerFilePayload structs.
|
||||||
|
for (const [name, value] of Object.entries(options.multipart)) {
|
||||||
|
if (isFilePayload(value)) {
|
||||||
|
const payload = value;
|
||||||
|
if (!Buffer.isBuffer(payload.buffer)) throw new Error(`Unexpected buffer type of 'data.${name}'`);
|
||||||
|
multipartData.push({
|
||||||
|
name,
|
||||||
|
file: filePayloadToJson(payload)
|
||||||
|
});
|
||||||
|
} else if (value instanceof _fs.default.ReadStream) {
|
||||||
|
multipartData.push({
|
||||||
|
name,
|
||||||
|
file: await readStreamToJson(value)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
multipartData.push({
|
||||||
|
name,
|
||||||
|
value: String(value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (postDataBuffer === undefined && jsonData === undefined && formData === undefined && multipartData === undefined) postDataBuffer = ((_options$request3 = options.request) === null || _options$request3 === void 0 ? void 0 : _options$request3.postDataBuffer()) || undefined;
|
||||||
|
const fixtures = {
|
||||||
|
__testHookLookup: options.__testHookLookup
|
||||||
|
};
|
||||||
|
const result = await this._channel.fetch({
|
||||||
|
url,
|
||||||
|
params,
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
postData: postDataBuffer,
|
||||||
|
jsonData,
|
||||||
|
formData,
|
||||||
|
multipartData,
|
||||||
|
timeout: options.timeout,
|
||||||
|
failOnStatusCode: options.failOnStatusCode,
|
||||||
|
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
|
||||||
|
maxRedirects: maxRedirects,
|
||||||
|
...fixtures
|
||||||
|
});
|
||||||
|
return new APIResponse(this, result.response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async storageState(options = {}) {
|
||||||
|
const state = await this._channel.storageState();
|
||||||
|
if (options.path) {
|
||||||
|
await (0, _fileUtils.mkdirIfNeeded)(options.path);
|
||||||
|
await _fs.default.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.APIRequestContext = APIRequestContext;
|
||||||
|
_util$inspect$custom = util.inspect.custom;
|
||||||
|
class APIResponse {
|
||||||
|
constructor(context, initializer) {
|
||||||
|
this._initializer = void 0;
|
||||||
|
this._headers = void 0;
|
||||||
|
this._request = void 0;
|
||||||
|
this._request = context;
|
||||||
|
this._initializer = initializer;
|
||||||
|
this._headers = new _network.RawHeaders(this._initializer.headers);
|
||||||
|
}
|
||||||
|
ok() {
|
||||||
|
return this._initializer.status >= 200 && this._initializer.status <= 299;
|
||||||
|
}
|
||||||
|
url() {
|
||||||
|
return this._initializer.url;
|
||||||
|
}
|
||||||
|
status() {
|
||||||
|
return this._initializer.status;
|
||||||
|
}
|
||||||
|
statusText() {
|
||||||
|
return this._initializer.statusText;
|
||||||
|
}
|
||||||
|
headers() {
|
||||||
|
return this._headers.headers();
|
||||||
|
}
|
||||||
|
headersArray() {
|
||||||
|
return this._headers.headersArray();
|
||||||
|
}
|
||||||
|
async body() {
|
||||||
|
try {
|
||||||
|
const result = await this._request._channel.fetchResponseBody({
|
||||||
|
fetchUid: this._fetchUid()
|
||||||
|
});
|
||||||
|
if (result.binary === undefined) throw new Error('Response has been disposed');
|
||||||
|
return result.binary;
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.includes(_errors.kBrowserOrContextClosedError)) throw new Error('Response has been disposed');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async text() {
|
||||||
|
const content = await this.body();
|
||||||
|
return content.toString('utf8');
|
||||||
|
}
|
||||||
|
async json() {
|
||||||
|
const content = await this.text();
|
||||||
|
return JSON.parse(content);
|
||||||
|
}
|
||||||
|
async dispose() {
|
||||||
|
await this._request._channel.disposeAPIResponse({
|
||||||
|
fetchUid: this._fetchUid()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
[_util$inspect$custom]() {
|
||||||
|
const headers = this.headersArray().map(({
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
}) => ` ${name}: ${value}`);
|
||||||
|
return `APIResponse: ${this.status()} ${this.statusText()}\n${headers.join('\n')}`;
|
||||||
|
}
|
||||||
|
_fetchUid() {
|
||||||
|
return this._initializer.fetchUid;
|
||||||
|
}
|
||||||
|
async _fetchLog() {
|
||||||
|
const {
|
||||||
|
log
|
||||||
|
} = await this._request._channel.fetchLog({
|
||||||
|
fetchUid: this._fetchUid()
|
||||||
|
});
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.APIResponse = APIResponse;
|
||||||
|
function filePayloadToJson(payload) {
|
||||||
|
return {
|
||||||
|
name: payload.name,
|
||||||
|
mimeType: payload.mimeType,
|
||||||
|
buffer: payload.buffer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function readStreamToJson(stream) {
|
||||||
|
const buffer = await new Promise((resolve, reject) => {
|
||||||
|
const chunks = [];
|
||||||
|
stream.on('data', chunk => chunks.push(chunk));
|
||||||
|
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
|
stream.on('error', err => reject(err));
|
||||||
|
});
|
||||||
|
const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString('utf8') : stream.path;
|
||||||
|
return {
|
||||||
|
name: _path.default.basename(streamPath),
|
||||||
|
buffer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function isJsonContentType(headers) {
|
||||||
|
if (!headers) return false;
|
||||||
|
for (const {
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
} of headers) {
|
||||||
|
if (name.toLocaleLowerCase() === 'content-type') return value === 'application/json';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function objectToArray(map) {
|
||||||
|
if (!map) return undefined;
|
||||||
|
const result = [];
|
||||||
|
for (const [name, value] of Object.entries(map)) result.push({
|
||||||
|
name,
|
||||||
|
value: String(value)
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function isFilePayload(value) {
|
||||||
|
return typeof value === 'object' && value['name'] && value['mimeType'] && value['buffer'];
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.FileChooser = void 0;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class FileChooser {
|
||||||
|
constructor(page, elementHandle, isMultiple) {
|
||||||
|
this._page = void 0;
|
||||||
|
this._elementHandle = void 0;
|
||||||
|
this._isMultiple = void 0;
|
||||||
|
this._page = page;
|
||||||
|
this._elementHandle = elementHandle;
|
||||||
|
this._isMultiple = isMultiple;
|
||||||
|
}
|
||||||
|
element() {
|
||||||
|
return this._elementHandle;
|
||||||
|
}
|
||||||
|
isMultiple() {
|
||||||
|
return this._isMultiple;
|
||||||
|
}
|
||||||
|
page() {
|
||||||
|
return this._page;
|
||||||
|
}
|
||||||
|
async setFiles(files, options) {
|
||||||
|
return this._elementHandle.setInputFiles(files, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.FileChooser = FileChooser;
|
@ -0,0 +1,515 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Frame = void 0;
|
||||||
|
exports.verifyLoadState = verifyLoadState;
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _locator = require("./locator");
|
||||||
|
var _locatorUtils = require("../utils/isomorphic/locatorUtils");
|
||||||
|
var _elementHandle = require("./elementHandle");
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var network = _interopRequireWildcard(require("./network"));
|
||||||
|
var _events = require("events");
|
||||||
|
var _waiter = require("./waiter");
|
||||||
|
var _events2 = require("./events");
|
||||||
|
var _types = require("./types");
|
||||||
|
var _network2 = require("../utils/network");
|
||||||
|
var _debugLogger = require("../common/debugLogger");
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Frame extends _channelOwner.ChannelOwner {
|
||||||
|
static from(frame) {
|
||||||
|
return frame._object;
|
||||||
|
}
|
||||||
|
static fromNullable(frame) {
|
||||||
|
return frame ? Frame.from(frame) : null;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._eventEmitter = void 0;
|
||||||
|
this._loadStates = void 0;
|
||||||
|
this._parentFrame = null;
|
||||||
|
this._url = '';
|
||||||
|
this._name = '';
|
||||||
|
this._detached = false;
|
||||||
|
this._childFrames = new Set();
|
||||||
|
this._page = void 0;
|
||||||
|
this._eventEmitter = new _events.EventEmitter();
|
||||||
|
this._eventEmitter.setMaxListeners(0);
|
||||||
|
this._parentFrame = Frame.fromNullable(initializer.parentFrame);
|
||||||
|
if (this._parentFrame) this._parentFrame._childFrames.add(this);
|
||||||
|
this._name = initializer.name;
|
||||||
|
this._url = initializer.url;
|
||||||
|
this._loadStates = new Set(initializer.loadStates);
|
||||||
|
this._channel.on('loadstate', event => {
|
||||||
|
if (event.add) {
|
||||||
|
this._loadStates.add(event.add);
|
||||||
|
this._eventEmitter.emit('loadstate', event.add);
|
||||||
|
}
|
||||||
|
if (event.remove) this._loadStates.delete(event.remove);
|
||||||
|
if (!this._parentFrame && event.add === 'load' && this._page) this._page.emit(_events2.Events.Page.Load, this._page);
|
||||||
|
if (!this._parentFrame && event.add === 'domcontentloaded' && this._page) this._page.emit(_events2.Events.Page.DOMContentLoaded, this._page);
|
||||||
|
});
|
||||||
|
this._channel.on('navigated', event => {
|
||||||
|
this._url = event.url;
|
||||||
|
this._name = event.name;
|
||||||
|
this._eventEmitter.emit('navigated', event);
|
||||||
|
if (!event.error && this._page) this._page.emit(_events2.Events.Page.FrameNavigated, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
page() {
|
||||||
|
return this._page;
|
||||||
|
}
|
||||||
|
async goto(url, options = {}) {
|
||||||
|
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
|
return network.Response.fromNullable((await this._channel.goto({
|
||||||
|
url,
|
||||||
|
...options,
|
||||||
|
waitUntil
|
||||||
|
})).response);
|
||||||
|
}
|
||||||
|
_setupNavigationWaiter(options) {
|
||||||
|
const waiter = new _waiter.Waiter(this._page, '');
|
||||||
|
if (this._page.isClosed()) waiter.rejectImmediately(new Error('Navigation failed because page was closed!'));
|
||||||
|
waiter.rejectOnEvent(this._page, _events2.Events.Page.Close, new Error('Navigation failed because page was closed!'));
|
||||||
|
waiter.rejectOnEvent(this._page, _events2.Events.Page.Crash, new Error('Navigation failed because page crashed!'));
|
||||||
|
waiter.rejectOnEvent(this._page, _events2.Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this);
|
||||||
|
const timeout = this._page._timeoutSettings.navigationTimeout(options);
|
||||||
|
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded.`);
|
||||||
|
return waiter;
|
||||||
|
}
|
||||||
|
async waitForNavigation(options = {}) {
|
||||||
|
return this._page._wrapApiCall(async () => {
|
||||||
|
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
|
const waiter = this._setupNavigationWaiter(options);
|
||||||
|
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
|
||||||
|
waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
|
||||||
|
const navigatedEvent = await waiter.waitForEvent(this._eventEmitter, 'navigated', event => {
|
||||||
|
var _this$_page;
|
||||||
|
// Any failed navigation results in a rejection.
|
||||||
|
if (event.error) return true;
|
||||||
|
waiter.log(` navigated to "${event.url}"`);
|
||||||
|
return (0, _network2.urlMatches)((_this$_page = this._page) === null || _this$_page === void 0 ? void 0 : _this$_page.context()._options.baseURL, event.url, options.url);
|
||||||
|
});
|
||||||
|
if (navigatedEvent.error) {
|
||||||
|
const e = new Error(navigatedEvent.error);
|
||||||
|
e.stack = '';
|
||||||
|
await waiter.waitForPromise(Promise.reject(e));
|
||||||
|
}
|
||||||
|
if (!this._loadStates.has(waitUntil)) {
|
||||||
|
await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => {
|
||||||
|
waiter.log(` "${s}" event fired`);
|
||||||
|
return s === waitUntil;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const request = navigatedEvent.newDocument ? network.Request.fromNullable(navigatedEvent.newDocument.request) : null;
|
||||||
|
const response = request ? await waiter.waitForPromise(request._finalRequest()._internalResponse()) : null;
|
||||||
|
waiter.dispose();
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async waitForLoadState(state = 'load', options = {}) {
|
||||||
|
state = verifyLoadState('state', state);
|
||||||
|
return this._page._wrapApiCall(async () => {
|
||||||
|
const waiter = this._setupNavigationWaiter(options);
|
||||||
|
if (this._loadStates.has(state)) {
|
||||||
|
waiter.log(` not waiting, "${state}" event already fired`);
|
||||||
|
} else {
|
||||||
|
await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => {
|
||||||
|
waiter.log(` "${s}" event fired`);
|
||||||
|
return s === state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
waiter.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async waitForURL(url, options = {}) {
|
||||||
|
var _this$_page2;
|
||||||
|
if ((0, _network2.urlMatches)((_this$_page2 = this._page) === null || _this$_page2 === void 0 ? void 0 : _this$_page2.context()._options.baseURL, this.url(), url)) return await this.waitForLoadState(options.waitUntil, options);
|
||||||
|
await this.waitForNavigation({
|
||||||
|
url,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async frameElement() {
|
||||||
|
return _elementHandle.ElementHandle.from((await this._channel.frameElement()).element);
|
||||||
|
}
|
||||||
|
async evaluateHandle(pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
|
||||||
|
const result = await this._channel.evaluateExpressionHandle({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return _jsHandle.JSHandle.from(result.handle);
|
||||||
|
}
|
||||||
|
async evaluate(pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
|
||||||
|
const result = await this._channel.evaluateExpression({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return (0, _jsHandle.parseResult)(result.value);
|
||||||
|
}
|
||||||
|
async _evaluateExposeUtilityScript(pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
|
||||||
|
const result = await this._channel.evaluateExpression({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
exposeUtilityScript: true,
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return (0, _jsHandle.parseResult)(result.value);
|
||||||
|
}
|
||||||
|
async $(selector, options) {
|
||||||
|
const result = await this._channel.querySelector({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
return _elementHandle.ElementHandle.fromNullable(result.element);
|
||||||
|
}
|
||||||
|
async waitForSelector(selector, options = {}) {
|
||||||
|
if (options.visibility) throw new Error('options.visibility is not supported, did you mean options.state?');
|
||||||
|
if (options.waitFor && options.waitFor !== 'visible') throw new Error('options.waitFor is not supported, did you mean options.state?');
|
||||||
|
const result = await this._channel.waitForSelector({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
return _elementHandle.ElementHandle.fromNullable(result.element);
|
||||||
|
}
|
||||||
|
async dispatchEvent(selector, type, eventInit, options = {}) {
|
||||||
|
await this._channel.dispatchEvent({
|
||||||
|
selector,
|
||||||
|
type,
|
||||||
|
eventInit: (0, _jsHandle.serializeArgument)(eventInit),
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async $eval(selector, pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
|
||||||
|
const result = await this._channel.evalOnSelector({
|
||||||
|
selector,
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return (0, _jsHandle.parseResult)(result.value);
|
||||||
|
}
|
||||||
|
async $$eval(selector, pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
|
||||||
|
const result = await this._channel.evalOnSelectorAll({
|
||||||
|
selector,
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return (0, _jsHandle.parseResult)(result.value);
|
||||||
|
}
|
||||||
|
async $$(selector) {
|
||||||
|
const result = await this._channel.querySelectorAll({
|
||||||
|
selector
|
||||||
|
});
|
||||||
|
return result.elements.map(e => _elementHandle.ElementHandle.from(e));
|
||||||
|
}
|
||||||
|
async _queryCount(selector) {
|
||||||
|
return (await this._channel.queryCount({
|
||||||
|
selector
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async content() {
|
||||||
|
return (await this._channel.content()).value;
|
||||||
|
}
|
||||||
|
async setContent(html, options = {}) {
|
||||||
|
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
|
await this._channel.setContent({
|
||||||
|
html,
|
||||||
|
...options,
|
||||||
|
waitUntil
|
||||||
|
});
|
||||||
|
}
|
||||||
|
name() {
|
||||||
|
return this._name || '';
|
||||||
|
}
|
||||||
|
url() {
|
||||||
|
return this._url;
|
||||||
|
}
|
||||||
|
parentFrame() {
|
||||||
|
return this._parentFrame;
|
||||||
|
}
|
||||||
|
childFrames() {
|
||||||
|
return Array.from(this._childFrames);
|
||||||
|
}
|
||||||
|
isDetached() {
|
||||||
|
return this._detached;
|
||||||
|
}
|
||||||
|
async addScriptTag(options = {}) {
|
||||||
|
const copy = {
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
if (copy.path) {
|
||||||
|
copy.content = (await _fs.default.promises.readFile(copy.path)).toString();
|
||||||
|
copy.content += '//# sourceURL=' + copy.path.replace(/\n/g, '');
|
||||||
|
}
|
||||||
|
return _elementHandle.ElementHandle.from((await this._channel.addScriptTag({
|
||||||
|
...copy
|
||||||
|
})).element);
|
||||||
|
}
|
||||||
|
async addStyleTag(options = {}) {
|
||||||
|
const copy = {
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
if (copy.path) {
|
||||||
|
copy.content = (await _fs.default.promises.readFile(copy.path)).toString();
|
||||||
|
copy.content += '/*# sourceURL=' + copy.path.replace(/\n/g, '') + '*/';
|
||||||
|
}
|
||||||
|
return _elementHandle.ElementHandle.from((await this._channel.addStyleTag({
|
||||||
|
...copy
|
||||||
|
})).element);
|
||||||
|
}
|
||||||
|
async click(selector, options = {}) {
|
||||||
|
return await this._channel.click({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async dblclick(selector, options = {}) {
|
||||||
|
return await this._channel.dblclick({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async dragAndDrop(source, target, options = {}) {
|
||||||
|
return await this._channel.dragAndDrop({
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async tap(selector, options = {}) {
|
||||||
|
return await this._channel.tap({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async fill(selector, value, options = {}) {
|
||||||
|
return await this._channel.fill({
|
||||||
|
selector,
|
||||||
|
value,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _highlight(selector) {
|
||||||
|
return await this._channel.highlight({
|
||||||
|
selector
|
||||||
|
});
|
||||||
|
}
|
||||||
|
locator(selector, options) {
|
||||||
|
return new _locator.Locator(this, selector, options);
|
||||||
|
}
|
||||||
|
getByTestId(testId) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTestIdSelector)((0, _locator.testIdAttributeName)(), testId));
|
||||||
|
}
|
||||||
|
getByAltText(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByLabel(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByPlaceholder(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByText(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByTitle(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByRole(role, options = {}) {
|
||||||
|
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
|
||||||
|
}
|
||||||
|
frameLocator(selector) {
|
||||||
|
return new _locator.FrameLocator(this, selector);
|
||||||
|
}
|
||||||
|
async focus(selector, options = {}) {
|
||||||
|
await this._channel.focus({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async textContent(selector, options = {}) {
|
||||||
|
const value = (await this._channel.textContent({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
return value === undefined ? null : value;
|
||||||
|
}
|
||||||
|
async innerText(selector, options = {}) {
|
||||||
|
return (await this._channel.innerText({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async innerHTML(selector, options = {}) {
|
||||||
|
return (await this._channel.innerHTML({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async getAttribute(selector, name, options = {}) {
|
||||||
|
const value = (await this._channel.getAttribute({
|
||||||
|
selector,
|
||||||
|
name,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
return value === undefined ? null : value;
|
||||||
|
}
|
||||||
|
async inputValue(selector, options = {}) {
|
||||||
|
return (await this._channel.inputValue({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async isChecked(selector, options = {}) {
|
||||||
|
return (await this._channel.isChecked({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async isDisabled(selector, options = {}) {
|
||||||
|
return (await this._channel.isDisabled({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async isEditable(selector, options = {}) {
|
||||||
|
return (await this._channel.isEditable({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async isEnabled(selector, options = {}) {
|
||||||
|
return (await this._channel.isEnabled({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async isHidden(selector, options = {}) {
|
||||||
|
return (await this._channel.isHidden({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async isVisible(selector, options = {}) {
|
||||||
|
return (await this._channel.isVisible({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
})).value;
|
||||||
|
}
|
||||||
|
async hover(selector, options = {}) {
|
||||||
|
await this._channel.hover({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async selectOption(selector, values, options = {}) {
|
||||||
|
return (await this._channel.selectOption({
|
||||||
|
selector,
|
||||||
|
...(0, _elementHandle.convertSelectOptionValues)(values),
|
||||||
|
...options
|
||||||
|
})).values;
|
||||||
|
}
|
||||||
|
async setInputFiles(selector, files, options = {}) {
|
||||||
|
const converted = await (0, _elementHandle.convertInputFiles)(files, this.page().context());
|
||||||
|
if (converted.files) {
|
||||||
|
await this._channel.setInputFiles({
|
||||||
|
selector,
|
||||||
|
files: converted.files,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_debugLogger.debugLogger.log('api', 'switching to large files mode');
|
||||||
|
await this._channel.setInputFilePaths({
|
||||||
|
selector,
|
||||||
|
...converted,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async type(selector, text, options = {}) {
|
||||||
|
await this._channel.type({
|
||||||
|
selector,
|
||||||
|
text,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async press(selector, key, options = {}) {
|
||||||
|
await this._channel.press({
|
||||||
|
selector,
|
||||||
|
key,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async check(selector, options = {}) {
|
||||||
|
await this._channel.check({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async uncheck(selector, options = {}) {
|
||||||
|
await this._channel.uncheck({
|
||||||
|
selector,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async setChecked(selector, checked, options) {
|
||||||
|
if (checked) await this.check(selector, options);else await this.uncheck(selector, options);
|
||||||
|
}
|
||||||
|
async waitForTimeout(timeout) {
|
||||||
|
await this._channel.waitForTimeout({
|
||||||
|
timeout
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async waitForFunction(pageFunction, arg, options = {}) {
|
||||||
|
if (typeof options.polling === 'string') (0, _utils.assert)(options.polling === 'raf', 'Unknown polling option: ' + options.polling);
|
||||||
|
const result = await this._channel.waitForFunction({
|
||||||
|
...options,
|
||||||
|
pollingInterval: options.polling === 'raf' ? undefined : options.polling,
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return _jsHandle.JSHandle.from(result.handle);
|
||||||
|
}
|
||||||
|
async title() {
|
||||||
|
return (await this._channel.title()).value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Frame = Frame;
|
||||||
|
function verifyLoadState(name, waitUntil) {
|
||||||
|
if (waitUntil === 'networkidle0') waitUntil = 'networkidle';
|
||||||
|
if (!_types.kLifecycleEvents.has(waitUntil)) throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
|
||||||
|
return waitUntil;
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.HarRouter = void 0;
|
||||||
|
var _debugLogger = require("../common/debugLogger");
|
||||||
|
var _events = require("./events");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HarRouter {
|
||||||
|
static async create(localUtils, file, notFoundAction, options) {
|
||||||
|
const {
|
||||||
|
harId,
|
||||||
|
error
|
||||||
|
} = await localUtils._channel.harOpen({
|
||||||
|
file
|
||||||
|
});
|
||||||
|
if (error) throw new Error(error);
|
||||||
|
return new HarRouter(localUtils, harId, notFoundAction, options);
|
||||||
|
}
|
||||||
|
constructor(localUtils, harId, notFoundAction, options) {
|
||||||
|
this._localUtils = void 0;
|
||||||
|
this._harId = void 0;
|
||||||
|
this._notFoundAction = void 0;
|
||||||
|
this._options = void 0;
|
||||||
|
this._localUtils = localUtils;
|
||||||
|
this._harId = harId;
|
||||||
|
this._options = options;
|
||||||
|
this._notFoundAction = notFoundAction;
|
||||||
|
}
|
||||||
|
async _handle(route) {
|
||||||
|
const request = route.request();
|
||||||
|
const response = await this._localUtils._channel.harLookup({
|
||||||
|
harId: this._harId,
|
||||||
|
url: request.url(),
|
||||||
|
method: request.method(),
|
||||||
|
headers: await request.headersArray(),
|
||||||
|
postData: request.postDataBuffer() || undefined,
|
||||||
|
isNavigationRequest: request.isNavigationRequest()
|
||||||
|
});
|
||||||
|
if (response.action === 'redirect') {
|
||||||
|
_debugLogger.debugLogger.log('api', `HAR: ${route.request().url()} redirected to ${response.redirectURL}`);
|
||||||
|
await route._redirectNavigationRequest(response.redirectURL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.action === 'fulfill') {
|
||||||
|
await route.fulfill({
|
||||||
|
status: response.status,
|
||||||
|
headers: Object.fromEntries(response.headers.map(h => [h.name, h.value])),
|
||||||
|
body: response.body
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.action === 'error') _debugLogger.debugLogger.log('api', 'HAR: ' + response.message);
|
||||||
|
// Report the error, but fall through to the default handler.
|
||||||
|
|
||||||
|
if (this._notFoundAction === 'abort') {
|
||||||
|
await route.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await route.fallback();
|
||||||
|
}
|
||||||
|
async addContextRoute(context) {
|
||||||
|
await context.route(this._options.urlMatch || '**/*', route => this._handle(route));
|
||||||
|
context.once(_events.Events.BrowserContext.Close, () => this.dispose());
|
||||||
|
}
|
||||||
|
async addPageRoute(page) {
|
||||||
|
await page.route(this._options.urlMatch || '**/*', route => this._handle(route));
|
||||||
|
page.once(_events.Events.Page.Close, () => this.dispose());
|
||||||
|
}
|
||||||
|
dispose() {
|
||||||
|
this._localUtils._channel.harClose({
|
||||||
|
harId: this._harId
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.HarRouter = HarRouter;
|
@ -0,0 +1,111 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Touchscreen = exports.Mouse = exports.Keyboard = void 0;
|
||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Keyboard {
|
||||||
|
constructor(page) {
|
||||||
|
this._page = void 0;
|
||||||
|
this._page = page;
|
||||||
|
}
|
||||||
|
async down(key) {
|
||||||
|
await this._page._channel.keyboardDown({
|
||||||
|
key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async up(key) {
|
||||||
|
await this._page._channel.keyboardUp({
|
||||||
|
key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async insertText(text) {
|
||||||
|
await this._page._channel.keyboardInsertText({
|
||||||
|
text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async type(text, options = {}) {
|
||||||
|
await this._page._channel.keyboardType({
|
||||||
|
text,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async press(key, options = {}) {
|
||||||
|
await this._page._channel.keyboardPress({
|
||||||
|
key,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Keyboard = Keyboard;
|
||||||
|
class Mouse {
|
||||||
|
constructor(page) {
|
||||||
|
this._page = void 0;
|
||||||
|
this._page = page;
|
||||||
|
}
|
||||||
|
async move(x, y, options = {}) {
|
||||||
|
await this._page._channel.mouseMove({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async down(options = {}) {
|
||||||
|
await this._page._channel.mouseDown({
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async up(options = {}) {
|
||||||
|
await this._page._channel.mouseUp(options);
|
||||||
|
}
|
||||||
|
async click(x, y, options = {}) {
|
||||||
|
await this._page._channel.mouseClick({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async dblclick(x, y, options = {}) {
|
||||||
|
await this.click(x, y, {
|
||||||
|
...options,
|
||||||
|
clickCount: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async wheel(deltaX, deltaY) {
|
||||||
|
await this._page._channel.mouseWheel({
|
||||||
|
deltaX,
|
||||||
|
deltaY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Mouse = Mouse;
|
||||||
|
class Touchscreen {
|
||||||
|
constructor(page) {
|
||||||
|
this._page = void 0;
|
||||||
|
this._page = page;
|
||||||
|
}
|
||||||
|
async tap(x, y) {
|
||||||
|
await this._page._channel.touchscreenTap({
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Touchscreen = Touchscreen;
|
@ -0,0 +1,119 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.JSHandle = void 0;
|
||||||
|
exports.assertMaxArguments = assertMaxArguments;
|
||||||
|
exports.parseResult = parseResult;
|
||||||
|
exports.serializeArgument = serializeArgument;
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _serializers = require("../protocol/serializers");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class JSHandle extends _channelOwner.ChannelOwner {
|
||||||
|
static from(handle) {
|
||||||
|
return handle._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._preview = void 0;
|
||||||
|
this._preview = this._initializer.preview;
|
||||||
|
this._channel.on('previewUpdated', ({
|
||||||
|
preview
|
||||||
|
}) => this._preview = preview);
|
||||||
|
}
|
||||||
|
async evaluate(pageFunction, arg) {
|
||||||
|
const result = await this._channel.evaluateExpression({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: serializeArgument(arg)
|
||||||
|
});
|
||||||
|
return parseResult(result.value);
|
||||||
|
}
|
||||||
|
async evaluateHandle(pageFunction, arg) {
|
||||||
|
const result = await this._channel.evaluateExpressionHandle({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: serializeArgument(arg)
|
||||||
|
});
|
||||||
|
return JSHandle.from(result.handle);
|
||||||
|
}
|
||||||
|
async getProperty(propertyName) {
|
||||||
|
const result = await this._channel.getProperty({
|
||||||
|
name: propertyName
|
||||||
|
});
|
||||||
|
return JSHandle.from(result.handle);
|
||||||
|
}
|
||||||
|
async getProperties() {
|
||||||
|
const map = new Map();
|
||||||
|
for (const {
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
} of (await this._channel.getPropertyList()).properties) map.set(name, JSHandle.from(value));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
async jsonValue() {
|
||||||
|
return parseResult((await this._channel.jsonValue()).value);
|
||||||
|
}
|
||||||
|
asElement() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
async dispose() {
|
||||||
|
return await this._channel.dispose();
|
||||||
|
}
|
||||||
|
async _objectCount() {
|
||||||
|
return this._wrapApiCall(async () => {
|
||||||
|
const {
|
||||||
|
count
|
||||||
|
} = await this._channel.objectCount();
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
return this._preview;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function takes care of converting all JSHandles to their channels,
|
||||||
|
// so that generic channel serializer converts them to guids.
|
||||||
|
exports.JSHandle = JSHandle;
|
||||||
|
function serializeArgument(arg) {
|
||||||
|
const handles = [];
|
||||||
|
const pushHandle = channel => {
|
||||||
|
handles.push(channel);
|
||||||
|
return handles.length - 1;
|
||||||
|
};
|
||||||
|
const value = (0, _serializers.serializeValue)(arg, value => {
|
||||||
|
if (value instanceof JSHandle) return {
|
||||||
|
h: pushHandle(value._channel)
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
fallThrough: value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
handles
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function parseResult(value) {
|
||||||
|
return (0, _serializers.parseSerializedValue)(value, undefined);
|
||||||
|
}
|
||||||
|
function assertMaxArguments(count, max) {
|
||||||
|
if (count > max) throw new Error('Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.');
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.JsonPipe = void 0;
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class JsonPipe extends _channelOwner.ChannelOwner {
|
||||||
|
static from(jsonPipe) {
|
||||||
|
return jsonPipe._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
}
|
||||||
|
channel() {
|
||||||
|
return this._channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.JsonPipe = JsonPipe;
|
@ -0,0 +1,29 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.LocalUtils = void 0;
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class LocalUtils extends _channelOwner.ChannelOwner {
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.LocalUtils = LocalUtils;
|
@ -0,0 +1,429 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Locator = exports.FrameLocator = void 0;
|
||||||
|
exports.setTestIdAttribute = setTestIdAttribute;
|
||||||
|
exports.testIdAttributeName = testIdAttributeName;
|
||||||
|
var util = _interopRequireWildcard(require("util"));
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _elementHandle = require("./elementHandle");
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _stringUtils = require("../utils/isomorphic/stringUtils");
|
||||||
|
var _locatorUtils = require("../utils/isomorphic/locatorUtils");
|
||||||
|
let _util$inspect$custom;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
_util$inspect$custom = util.inspect.custom;
|
||||||
|
class Locator {
|
||||||
|
constructor(frame, selector, options) {
|
||||||
|
this._frame = void 0;
|
||||||
|
this._selector = void 0;
|
||||||
|
this._frame = frame;
|
||||||
|
this._selector = selector;
|
||||||
|
if (options !== null && options !== void 0 && options.hasText) this._selector += ` >> internal:has-text=${(0, _stringUtils.escapeForTextSelector)(options.hasText, false)}`;
|
||||||
|
if (options !== null && options !== void 0 && options.hasNotText) this._selector += ` >> internal:has-not-text=${(0, _stringUtils.escapeForTextSelector)(options.hasNotText, false)}`;
|
||||||
|
if (options !== null && options !== void 0 && options.has) {
|
||||||
|
const locator = options.has;
|
||||||
|
if (locator._frame !== frame) throw new Error(`Inner "has" locator must belong to the same frame.`);
|
||||||
|
this._selector += ` >> internal:has=` + JSON.stringify(locator._selector);
|
||||||
|
}
|
||||||
|
if (options !== null && options !== void 0 && options.hasNot) {
|
||||||
|
const locator = options.hasNot;
|
||||||
|
if (locator._frame !== frame) throw new Error(`Inner "hasNot" locator must belong to the same frame.`);
|
||||||
|
this._selector += ` >> internal:has-not=` + JSON.stringify(locator._selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async _withElement(task, timeout) {
|
||||||
|
timeout = this._frame.page()._timeoutSettings.timeout({
|
||||||
|
timeout
|
||||||
|
});
|
||||||
|
const deadline = timeout ? (0, _utils.monotonicTime)() + timeout : 0;
|
||||||
|
return this._frame._wrapApiCall(async () => {
|
||||||
|
const result = await this._frame._channel.waitForSelector({
|
||||||
|
selector: this._selector,
|
||||||
|
strict: true,
|
||||||
|
state: 'attached',
|
||||||
|
timeout
|
||||||
|
});
|
||||||
|
const handle = _elementHandle.ElementHandle.fromNullable(result.element);
|
||||||
|
if (!handle) throw new Error(`Could not resolve ${this._selector} to DOM Element`);
|
||||||
|
try {
|
||||||
|
return await task(handle, deadline ? deadline - (0, _utils.monotonicTime)() : 0);
|
||||||
|
} finally {
|
||||||
|
await handle.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
page() {
|
||||||
|
return this._frame.page();
|
||||||
|
}
|
||||||
|
async boundingBox(options) {
|
||||||
|
return this._withElement(h => h.boundingBox(), options === null || options === void 0 ? void 0 : options.timeout);
|
||||||
|
}
|
||||||
|
async check(options = {}) {
|
||||||
|
return this._frame.check(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async click(options = {}) {
|
||||||
|
return this._frame.click(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async dblclick(options = {}) {
|
||||||
|
return this._frame.dblclick(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async dispatchEvent(type, eventInit = {}, options) {
|
||||||
|
return this._frame.dispatchEvent(this._selector, type, eventInit, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async dragTo(target, options = {}) {
|
||||||
|
return this._frame.dragAndDrop(this._selector, target._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async evaluate(pageFunction, arg, options) {
|
||||||
|
return this._withElement(h => h.evaluate(pageFunction, arg), options === null || options === void 0 ? void 0 : options.timeout);
|
||||||
|
}
|
||||||
|
async evaluateAll(pageFunction, arg) {
|
||||||
|
return this._frame.$$eval(this._selector, pageFunction, arg);
|
||||||
|
}
|
||||||
|
async evaluateHandle(pageFunction, arg, options) {
|
||||||
|
return this._withElement(h => h.evaluateHandle(pageFunction, arg), options === null || options === void 0 ? void 0 : options.timeout);
|
||||||
|
}
|
||||||
|
async fill(value, options = {}) {
|
||||||
|
return this._frame.fill(this._selector, value, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async clear(options = {}) {
|
||||||
|
return this.fill('', options);
|
||||||
|
}
|
||||||
|
async _highlight() {
|
||||||
|
// VS Code extension uses this one, keep it for now.
|
||||||
|
return this._frame._highlight(this._selector);
|
||||||
|
}
|
||||||
|
async highlight() {
|
||||||
|
return this._frame._highlight(this._selector);
|
||||||
|
}
|
||||||
|
locator(selectorOrLocator, options) {
|
||||||
|
if ((0, _utils.isString)(selectorOrLocator)) return new Locator(this._frame, this._selector + ' >> ' + selectorOrLocator, options);
|
||||||
|
if (selectorOrLocator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
|
||||||
|
return new Locator(this._frame, this._selector + ' >> internal:chain=' + JSON.stringify(selectorOrLocator._selector), options);
|
||||||
|
}
|
||||||
|
getByTestId(testId) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
|
||||||
|
}
|
||||||
|
getByAltText(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByLabel(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByPlaceholder(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByText(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByTitle(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByRole(role, options = {}) {
|
||||||
|
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
|
||||||
|
}
|
||||||
|
frameLocator(selector) {
|
||||||
|
return new FrameLocator(this._frame, this._selector + ' >> ' + selector);
|
||||||
|
}
|
||||||
|
filter(options) {
|
||||||
|
return new Locator(this._frame, this._selector, options);
|
||||||
|
}
|
||||||
|
async elementHandle(options) {
|
||||||
|
return await this._frame.waitForSelector(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
state: 'attached',
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async elementHandles() {
|
||||||
|
return this._frame.$$(this._selector);
|
||||||
|
}
|
||||||
|
first() {
|
||||||
|
return new Locator(this._frame, this._selector + ' >> nth=0');
|
||||||
|
}
|
||||||
|
last() {
|
||||||
|
return new Locator(this._frame, this._selector + ` >> nth=-1`);
|
||||||
|
}
|
||||||
|
nth(index) {
|
||||||
|
return new Locator(this._frame, this._selector + ` >> nth=${index}`);
|
||||||
|
}
|
||||||
|
and(locator) {
|
||||||
|
if (locator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
|
||||||
|
return new Locator(this._frame, this._selector + ` >> internal:and=` + JSON.stringify(locator._selector));
|
||||||
|
}
|
||||||
|
or(locator) {
|
||||||
|
if (locator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
|
||||||
|
return new Locator(this._frame, this._selector + ` >> internal:or=` + JSON.stringify(locator._selector));
|
||||||
|
}
|
||||||
|
async focus(options) {
|
||||||
|
return this._frame.focus(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async blur(options) {
|
||||||
|
await this._frame._channel.blur({
|
||||||
|
selector: this._selector,
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async count() {
|
||||||
|
return this._frame._queryCount(this._selector);
|
||||||
|
}
|
||||||
|
async getAttribute(name, options) {
|
||||||
|
return this._frame.getAttribute(this._selector, name, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async hover(options = {}) {
|
||||||
|
return this._frame.hover(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async innerHTML(options) {
|
||||||
|
return this._frame.innerHTML(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async innerText(options) {
|
||||||
|
return this._frame.innerText(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async inputValue(options) {
|
||||||
|
return this._frame.inputValue(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async isChecked(options) {
|
||||||
|
return this._frame.isChecked(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async isDisabled(options) {
|
||||||
|
return this._frame.isDisabled(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async isEditable(options) {
|
||||||
|
return this._frame.isEditable(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async isEnabled(options) {
|
||||||
|
return this._frame.isEnabled(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async isHidden(options) {
|
||||||
|
return this._frame.isHidden(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async isVisible(options) {
|
||||||
|
return this._frame.isVisible(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async press(key, options = {}) {
|
||||||
|
return this._frame.press(this._selector, key, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
return this._withElement((h, timeout) => h.screenshot({
|
||||||
|
...options,
|
||||||
|
timeout
|
||||||
|
}), options.timeout);
|
||||||
|
}
|
||||||
|
async scrollIntoViewIfNeeded(options = {}) {
|
||||||
|
return this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({
|
||||||
|
...options,
|
||||||
|
timeout
|
||||||
|
}), options.timeout);
|
||||||
|
}
|
||||||
|
async selectOption(values, options = {}) {
|
||||||
|
return this._frame.selectOption(this._selector, values, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async selectText(options = {}) {
|
||||||
|
return this._withElement((h, timeout) => h.selectText({
|
||||||
|
...options,
|
||||||
|
timeout
|
||||||
|
}), options.timeout);
|
||||||
|
}
|
||||||
|
async setChecked(checked, options) {
|
||||||
|
if (checked) await this.check(options);else await this.uncheck(options);
|
||||||
|
}
|
||||||
|
async setInputFiles(files, options = {}) {
|
||||||
|
return this._frame.setInputFiles(this._selector, files, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async tap(options = {}) {
|
||||||
|
return this._frame.tap(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async textContent(options) {
|
||||||
|
return this._frame.textContent(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async type(text, options = {}) {
|
||||||
|
return this._frame.type(this._selector, text, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async uncheck(options = {}) {
|
||||||
|
return this._frame.uncheck(this._selector, {
|
||||||
|
strict: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async all() {
|
||||||
|
return new Array(await this.count()).fill(0).map((e, i) => this.nth(i));
|
||||||
|
}
|
||||||
|
async allInnerTexts() {
|
||||||
|
return this._frame.$$eval(this._selector, ee => ee.map(e => e.innerText));
|
||||||
|
}
|
||||||
|
async allTextContents() {
|
||||||
|
return this._frame.$$eval(this._selector, ee => ee.map(e => e.textContent || ''));
|
||||||
|
}
|
||||||
|
async waitFor(options) {
|
||||||
|
await this._frame._channel.waitForSelector({
|
||||||
|
selector: this._selector,
|
||||||
|
strict: true,
|
||||||
|
omitReturnValue: true,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _expect(expression, options) {
|
||||||
|
const params = {
|
||||||
|
selector: this._selector,
|
||||||
|
expression,
|
||||||
|
...options,
|
||||||
|
isNot: !!options.isNot
|
||||||
|
};
|
||||||
|
params.expectedValue = (0, _jsHandle.serializeArgument)(options.expectedValue);
|
||||||
|
const result = await this._frame._channel.expect(params);
|
||||||
|
if (result.received !== undefined) result.received = (0, _jsHandle.parseResult)(result.received);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
[_util$inspect$custom]() {
|
||||||
|
return this.toString();
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
return `Locator@${this._selector}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Locator = Locator;
|
||||||
|
class FrameLocator {
|
||||||
|
constructor(frame, selector) {
|
||||||
|
this._frame = void 0;
|
||||||
|
this._frameSelector = void 0;
|
||||||
|
this._frame = frame;
|
||||||
|
this._frameSelector = selector;
|
||||||
|
}
|
||||||
|
locator(selectorOrLocator, options) {
|
||||||
|
if ((0, _utils.isString)(selectorOrLocator)) return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selectorOrLocator, options);
|
||||||
|
if (selectorOrLocator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
|
||||||
|
return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selectorOrLocator._selector, options);
|
||||||
|
}
|
||||||
|
getByTestId(testId) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
|
||||||
|
}
|
||||||
|
getByAltText(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByLabel(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByPlaceholder(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByText(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByTitle(text, options) {
|
||||||
|
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
|
||||||
|
}
|
||||||
|
getByRole(role, options = {}) {
|
||||||
|
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
|
||||||
|
}
|
||||||
|
frameLocator(selector) {
|
||||||
|
return new FrameLocator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selector);
|
||||||
|
}
|
||||||
|
first() {
|
||||||
|
return new FrameLocator(this._frame, this._frameSelector + ' >> nth=0');
|
||||||
|
}
|
||||||
|
last() {
|
||||||
|
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=-1`);
|
||||||
|
}
|
||||||
|
nth(index) {
|
||||||
|
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=${index}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.FrameLocator = FrameLocator;
|
||||||
|
let _testIdAttributeName = 'data-testid';
|
||||||
|
function testIdAttributeName() {
|
||||||
|
return _testIdAttributeName;
|
||||||
|
}
|
||||||
|
function setTestIdAttribute(attributeName) {
|
||||||
|
_testIdAttributeName = attributeName;
|
||||||
|
}
|
@ -0,0 +1,552 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.WebSocket = exports.RouteHandler = exports.Route = exports.Response = exports.Request = exports.RawHeaders = void 0;
|
||||||
|
exports.validateHeaders = validateHeaders;
|
||||||
|
var _url = require("url");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _frame = require("./frame");
|
||||||
|
var _worker = require("./worker");
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _utilsBundle = require("../utilsBundle");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _manualPromise = require("../utils/manualPromise");
|
||||||
|
var _events = require("./events");
|
||||||
|
var _waiter = require("./waiter");
|
||||||
|
var _network = require("../utils/network");
|
||||||
|
var _multimap = require("../utils/multimap");
|
||||||
|
var _fetch = require("./fetch");
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Request extends _channelOwner.ChannelOwner {
|
||||||
|
static from(request) {
|
||||||
|
return request._object;
|
||||||
|
}
|
||||||
|
static fromNullable(request) {
|
||||||
|
return request ? Request.from(request) : null;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._redirectedFrom = null;
|
||||||
|
this._redirectedTo = null;
|
||||||
|
this._failureText = null;
|
||||||
|
this._provisionalHeaders = void 0;
|
||||||
|
this._actualHeadersPromise = void 0;
|
||||||
|
this._timing = void 0;
|
||||||
|
this._fallbackOverrides = {};
|
||||||
|
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
|
||||||
|
if (this._redirectedFrom) this._redirectedFrom._redirectedTo = this;
|
||||||
|
this._provisionalHeaders = new RawHeaders(initializer.headers);
|
||||||
|
this._fallbackOverrides.postDataBuffer = initializer.postData;
|
||||||
|
this._timing = {
|
||||||
|
startTime: 0,
|
||||||
|
domainLookupStart: -1,
|
||||||
|
domainLookupEnd: -1,
|
||||||
|
connectStart: -1,
|
||||||
|
secureConnectionStart: -1,
|
||||||
|
connectEnd: -1,
|
||||||
|
requestStart: -1,
|
||||||
|
responseStart: -1,
|
||||||
|
responseEnd: -1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
url() {
|
||||||
|
return this._fallbackOverrides.url || this._initializer.url;
|
||||||
|
}
|
||||||
|
resourceType() {
|
||||||
|
return this._initializer.resourceType;
|
||||||
|
}
|
||||||
|
method() {
|
||||||
|
return this._fallbackOverrides.method || this._initializer.method;
|
||||||
|
}
|
||||||
|
postData() {
|
||||||
|
var _this$_fallbackOverri;
|
||||||
|
return ((_this$_fallbackOverri = this._fallbackOverrides.postDataBuffer) === null || _this$_fallbackOverri === void 0 ? void 0 : _this$_fallbackOverri.toString('utf-8')) || null;
|
||||||
|
}
|
||||||
|
postDataBuffer() {
|
||||||
|
return this._fallbackOverrides.postDataBuffer || null;
|
||||||
|
}
|
||||||
|
postDataJSON() {
|
||||||
|
const postData = this.postData();
|
||||||
|
if (!postData) return null;
|
||||||
|
const contentType = this.headers()['content-type'];
|
||||||
|
if (contentType === 'application/x-www-form-urlencoded') {
|
||||||
|
const entries = {};
|
||||||
|
const parsed = new _url.URLSearchParams(postData);
|
||||||
|
for (const [k, v] of parsed.entries()) entries[k] = v;
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(postData);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('POST data is not a valid JSON object: ' + postData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
headers() {
|
||||||
|
if (this._fallbackOverrides.headers) return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers).headers();
|
||||||
|
return this._provisionalHeaders.headers();
|
||||||
|
}
|
||||||
|
_context() {
|
||||||
|
// TODO: make sure this works for service worker requests.
|
||||||
|
return this.frame().page().context();
|
||||||
|
}
|
||||||
|
_actualHeaders() {
|
||||||
|
if (this._fallbackOverrides.headers) return Promise.resolve(RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers));
|
||||||
|
if (!this._actualHeadersPromise) {
|
||||||
|
this._actualHeadersPromise = this._wrapApiCall(async () => {
|
||||||
|
return new RawHeaders((await this._channel.rawRequestHeaders()).headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._actualHeadersPromise;
|
||||||
|
}
|
||||||
|
async allHeaders() {
|
||||||
|
return (await this._actualHeaders()).headers();
|
||||||
|
}
|
||||||
|
async headersArray() {
|
||||||
|
return (await this._actualHeaders()).headersArray();
|
||||||
|
}
|
||||||
|
async headerValue(name) {
|
||||||
|
return (await this._actualHeaders()).get(name);
|
||||||
|
}
|
||||||
|
async response() {
|
||||||
|
return Response.fromNullable((await this._channel.response()).response);
|
||||||
|
}
|
||||||
|
async _internalResponse() {
|
||||||
|
return this._wrapApiCall(async () => {
|
||||||
|
return Response.fromNullable((await this._channel.response()).response);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
frame() {
|
||||||
|
if (!this._initializer.frame) {
|
||||||
|
(0, _utils.assert)(this.serviceWorker());
|
||||||
|
throw new Error('Service Worker requests do not have an associated frame.');
|
||||||
|
}
|
||||||
|
return _frame.Frame.from(this._initializer.frame);
|
||||||
|
}
|
||||||
|
serviceWorker() {
|
||||||
|
return this._initializer.serviceWorker ? _worker.Worker.from(this._initializer.serviceWorker) : null;
|
||||||
|
}
|
||||||
|
isNavigationRequest() {
|
||||||
|
return this._initializer.isNavigationRequest;
|
||||||
|
}
|
||||||
|
redirectedFrom() {
|
||||||
|
return this._redirectedFrom;
|
||||||
|
}
|
||||||
|
redirectedTo() {
|
||||||
|
return this._redirectedTo;
|
||||||
|
}
|
||||||
|
failure() {
|
||||||
|
if (this._failureText === null) return null;
|
||||||
|
return {
|
||||||
|
errorText: this._failureText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
timing() {
|
||||||
|
return this._timing;
|
||||||
|
}
|
||||||
|
async sizes() {
|
||||||
|
const response = await this.response();
|
||||||
|
if (!response) throw new Error('Unable to fetch sizes for failed request');
|
||||||
|
return (await response._channel.sizes()).sizes;
|
||||||
|
}
|
||||||
|
_setResponseEndTiming(responseEndTiming) {
|
||||||
|
this._timing.responseEnd = responseEndTiming;
|
||||||
|
if (this._timing.responseStart === -1) this._timing.responseStart = responseEndTiming;
|
||||||
|
}
|
||||||
|
_finalRequest() {
|
||||||
|
return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
|
||||||
|
}
|
||||||
|
_applyFallbackOverrides(overrides) {
|
||||||
|
if (overrides.url) this._fallbackOverrides.url = overrides.url;
|
||||||
|
if (overrides.method) this._fallbackOverrides.method = overrides.method;
|
||||||
|
if (overrides.headers) this._fallbackOverrides.headers = overrides.headers;
|
||||||
|
if ((0, _utils.isString)(overrides.postData)) this._fallbackOverrides.postDataBuffer = Buffer.from(overrides.postData, 'utf-8');else if (overrides.postData instanceof Buffer) this._fallbackOverrides.postDataBuffer = overrides.postData;else if (overrides.postData) this._fallbackOverrides.postDataBuffer = Buffer.from(JSON.stringify(overrides.postData), 'utf-8');
|
||||||
|
}
|
||||||
|
_fallbackOverridesForContinue() {
|
||||||
|
return this._fallbackOverrides;
|
||||||
|
}
|
||||||
|
_targetClosedScope() {
|
||||||
|
var _this$serviceWorker, _this$frame$_page;
|
||||||
|
return ((_this$serviceWorker = this.serviceWorker()) === null || _this$serviceWorker === void 0 ? void 0 : _this$serviceWorker._closedScope) || ((_this$frame$_page = this.frame()._page) === null || _this$frame$_page === void 0 ? void 0 : _this$frame$_page._closedOrCrashedScope) || new _manualPromise.LongStandingScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Request = Request;
|
||||||
|
class Route extends _channelOwner.ChannelOwner {
|
||||||
|
static from(route) {
|
||||||
|
return route._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._handlingPromise = null;
|
||||||
|
}
|
||||||
|
request() {
|
||||||
|
return Request.from(this._initializer.request);
|
||||||
|
}
|
||||||
|
_raceWithTargetClose(promise) {
|
||||||
|
// When page closes or crashes, we catch any potential rejects from this Route.
|
||||||
|
// Note that page could be missing when routing popup's initial request that
|
||||||
|
// does not have a Page initialized just yet.
|
||||||
|
return this.request()._targetClosedScope().safeRace(promise);
|
||||||
|
}
|
||||||
|
_startHandling() {
|
||||||
|
this._handlingPromise = new _manualPromise.ManualPromise();
|
||||||
|
return this._handlingPromise;
|
||||||
|
}
|
||||||
|
async fallback(options = {}) {
|
||||||
|
this._checkNotHandled();
|
||||||
|
this.request()._applyFallbackOverrides(options);
|
||||||
|
this._reportHandled(false);
|
||||||
|
}
|
||||||
|
async abort(errorCode) {
|
||||||
|
this._checkNotHandled();
|
||||||
|
await this._raceWithTargetClose(this._channel.abort({
|
||||||
|
requestUrl: this.request()._initializer.url,
|
||||||
|
errorCode
|
||||||
|
}));
|
||||||
|
this._reportHandled(true);
|
||||||
|
}
|
||||||
|
async _redirectNavigationRequest(url) {
|
||||||
|
this._checkNotHandled();
|
||||||
|
await this._raceWithTargetClose(this._channel.redirectNavigationRequest({
|
||||||
|
url
|
||||||
|
}));
|
||||||
|
this._reportHandled(true);
|
||||||
|
}
|
||||||
|
async fetch(options = {}) {
|
||||||
|
return await this._wrapApiCall(async () => {
|
||||||
|
const context = this.request()._context();
|
||||||
|
return context.request._innerFetch({
|
||||||
|
request: this.request(),
|
||||||
|
data: options.postData,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async fulfill(options = {}) {
|
||||||
|
this._checkNotHandled();
|
||||||
|
await this._wrapApiCall(async () => {
|
||||||
|
await this._innerFulfill(options);
|
||||||
|
this._reportHandled(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _innerFulfill(options = {}) {
|
||||||
|
let fetchResponseUid;
|
||||||
|
let {
|
||||||
|
status: statusOption,
|
||||||
|
headers: headersOption,
|
||||||
|
body
|
||||||
|
} = options;
|
||||||
|
if (options.json !== undefined) {
|
||||||
|
(0, _utils.assert)(options.body === undefined, 'Can specify either body or json parameters');
|
||||||
|
body = JSON.stringify(options.json);
|
||||||
|
}
|
||||||
|
if (options.response instanceof _fetch.APIResponse) {
|
||||||
|
var _statusOption, _headersOption;
|
||||||
|
(_statusOption = statusOption) !== null && _statusOption !== void 0 ? _statusOption : statusOption = options.response.status();
|
||||||
|
(_headersOption = headersOption) !== null && _headersOption !== void 0 ? _headersOption : headersOption = options.response.headers();
|
||||||
|
if (body === undefined && options.path === undefined) {
|
||||||
|
if (options.response._request._connection === this._connection) fetchResponseUid = options.response._fetchUid();else body = await options.response.body();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let isBase64 = false;
|
||||||
|
let length = 0;
|
||||||
|
if (options.path) {
|
||||||
|
const buffer = await _fs.default.promises.readFile(options.path);
|
||||||
|
body = buffer.toString('base64');
|
||||||
|
isBase64 = true;
|
||||||
|
length = buffer.length;
|
||||||
|
} else if ((0, _utils.isString)(body)) {
|
||||||
|
isBase64 = false;
|
||||||
|
length = Buffer.byteLength(body);
|
||||||
|
} else if (body) {
|
||||||
|
length = body.length;
|
||||||
|
body = body.toString('base64');
|
||||||
|
isBase64 = true;
|
||||||
|
}
|
||||||
|
const headers = {};
|
||||||
|
for (const header of Object.keys(headersOption || {})) headers[header.toLowerCase()] = String(headersOption[header]);
|
||||||
|
if (options.contentType) headers['content-type'] = String(options.contentType);else if (options.json) headers['content-type'] = 'application/json';else if (options.path) headers['content-type'] = _utilsBundle.mime.getType(options.path) || 'application/octet-stream';
|
||||||
|
if (length && !('content-length' in headers)) headers['content-length'] = String(length);
|
||||||
|
await this._raceWithTargetClose(this._channel.fulfill({
|
||||||
|
requestUrl: this.request()._initializer.url,
|
||||||
|
status: statusOption || 200,
|
||||||
|
headers: (0, _utils.headersObjectToArray)(headers),
|
||||||
|
body,
|
||||||
|
isBase64,
|
||||||
|
fetchResponseUid
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
async continue(options = {}) {
|
||||||
|
this._checkNotHandled();
|
||||||
|
this.request()._applyFallbackOverrides(options);
|
||||||
|
await this._innerContinue();
|
||||||
|
this._reportHandled(true);
|
||||||
|
}
|
||||||
|
_checkNotHandled() {
|
||||||
|
if (!this._handlingPromise) throw new Error('Route is already handled!');
|
||||||
|
}
|
||||||
|
_reportHandled(done) {
|
||||||
|
const chain = this._handlingPromise;
|
||||||
|
this._handlingPromise = null;
|
||||||
|
chain.resolve(done);
|
||||||
|
}
|
||||||
|
async _innerContinue(internal = false) {
|
||||||
|
const options = this.request()._fallbackOverridesForContinue();
|
||||||
|
return await this._wrapApiCall(async () => {
|
||||||
|
await this._raceWithTargetClose(this._channel.continue({
|
||||||
|
requestUrl: this.request()._initializer.url,
|
||||||
|
url: options.url,
|
||||||
|
method: options.method,
|
||||||
|
headers: options.headers ? (0, _utils.headersObjectToArray)(options.headers) : undefined,
|
||||||
|
postData: options.postDataBuffer,
|
||||||
|
isFallback: internal
|
||||||
|
}));
|
||||||
|
}, !!internal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Route = Route;
|
||||||
|
class Response extends _channelOwner.ChannelOwner {
|
||||||
|
static from(response) {
|
||||||
|
return response._object;
|
||||||
|
}
|
||||||
|
static fromNullable(response) {
|
||||||
|
return response ? Response.from(response) : null;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._provisionalHeaders = void 0;
|
||||||
|
this._actualHeadersPromise = void 0;
|
||||||
|
this._request = void 0;
|
||||||
|
this._finishedPromise = new _manualPromise.ManualPromise();
|
||||||
|
this._provisionalHeaders = new RawHeaders(initializer.headers);
|
||||||
|
this._request = Request.from(this._initializer.request);
|
||||||
|
Object.assign(this._request._timing, this._initializer.timing);
|
||||||
|
}
|
||||||
|
url() {
|
||||||
|
return this._initializer.url;
|
||||||
|
}
|
||||||
|
ok() {
|
||||||
|
// Status 0 is for file:// URLs
|
||||||
|
return this._initializer.status === 0 || this._initializer.status >= 200 && this._initializer.status <= 299;
|
||||||
|
}
|
||||||
|
status() {
|
||||||
|
return this._initializer.status;
|
||||||
|
}
|
||||||
|
statusText() {
|
||||||
|
return this._initializer.statusText;
|
||||||
|
}
|
||||||
|
fromServiceWorker() {
|
||||||
|
return this._initializer.fromServiceWorker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
headers() {
|
||||||
|
return this._provisionalHeaders.headers();
|
||||||
|
}
|
||||||
|
async _actualHeaders() {
|
||||||
|
if (!this._actualHeadersPromise) {
|
||||||
|
this._actualHeadersPromise = (async () => {
|
||||||
|
return new RawHeaders((await this._channel.rawResponseHeaders()).headers);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
return this._actualHeadersPromise;
|
||||||
|
}
|
||||||
|
async allHeaders() {
|
||||||
|
return (await this._actualHeaders()).headers();
|
||||||
|
}
|
||||||
|
async headersArray() {
|
||||||
|
return (await this._actualHeaders()).headersArray().slice();
|
||||||
|
}
|
||||||
|
async headerValue(name) {
|
||||||
|
return (await this._actualHeaders()).get(name);
|
||||||
|
}
|
||||||
|
async headerValues(name) {
|
||||||
|
return (await this._actualHeaders()).getAll(name);
|
||||||
|
}
|
||||||
|
async finished() {
|
||||||
|
return this.request()._targetClosedScope().race(this._finishedPromise);
|
||||||
|
}
|
||||||
|
async body() {
|
||||||
|
return (await this._channel.body()).binary;
|
||||||
|
}
|
||||||
|
async text() {
|
||||||
|
const content = await this.body();
|
||||||
|
return content.toString('utf8');
|
||||||
|
}
|
||||||
|
async json() {
|
||||||
|
const content = await this.text();
|
||||||
|
return JSON.parse(content);
|
||||||
|
}
|
||||||
|
request() {
|
||||||
|
return this._request;
|
||||||
|
}
|
||||||
|
frame() {
|
||||||
|
return this._request.frame();
|
||||||
|
}
|
||||||
|
async serverAddr() {
|
||||||
|
return (await this._channel.serverAddr()).value || null;
|
||||||
|
}
|
||||||
|
async securityDetails() {
|
||||||
|
return (await this._channel.securityDetails()).value || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Response = Response;
|
||||||
|
class WebSocket extends _channelOwner.ChannelOwner {
|
||||||
|
static from(webSocket) {
|
||||||
|
return webSocket._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._page = void 0;
|
||||||
|
this._isClosed = void 0;
|
||||||
|
this._isClosed = false;
|
||||||
|
this._page = parent;
|
||||||
|
this._channel.on('frameSent', event => {
|
||||||
|
if (event.opcode === 1) this.emit(_events.Events.WebSocket.FrameSent, {
|
||||||
|
payload: event.data
|
||||||
|
});else if (event.opcode === 2) this.emit(_events.Events.WebSocket.FrameSent, {
|
||||||
|
payload: Buffer.from(event.data, 'base64')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._channel.on('frameReceived', event => {
|
||||||
|
if (event.opcode === 1) this.emit(_events.Events.WebSocket.FrameReceived, {
|
||||||
|
payload: event.data
|
||||||
|
});else if (event.opcode === 2) this.emit(_events.Events.WebSocket.FrameReceived, {
|
||||||
|
payload: Buffer.from(event.data, 'base64')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._channel.on('socketError', ({
|
||||||
|
error
|
||||||
|
}) => this.emit(_events.Events.WebSocket.Error, error));
|
||||||
|
this._channel.on('close', () => {
|
||||||
|
this._isClosed = true;
|
||||||
|
this.emit(_events.Events.WebSocket.Close, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
url() {
|
||||||
|
return this._initializer.url;
|
||||||
|
}
|
||||||
|
isClosed() {
|
||||||
|
return this._isClosed;
|
||||||
|
}
|
||||||
|
async waitForEvent(event, optionsOrPredicate = {}) {
|
||||||
|
return this._wrapApiCall(async () => {
|
||||||
|
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
|
const waiter = _waiter.Waiter.createForEvent(this, event);
|
||||||
|
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
|
||||||
|
if (event !== _events.Events.WebSocket.Error) waiter.rejectOnEvent(this, _events.Events.WebSocket.Error, new Error('Socket error'));
|
||||||
|
if (event !== _events.Events.WebSocket.Close) waiter.rejectOnEvent(this, _events.Events.WebSocket.Close, new Error('Socket closed'));
|
||||||
|
waiter.rejectOnEvent(this._page, _events.Events.Page.Close, new Error('Page closed'));
|
||||||
|
const result = await waiter.waitForEvent(this, event, predicate);
|
||||||
|
waiter.dispose();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.WebSocket = WebSocket;
|
||||||
|
function validateHeaders(headers) {
|
||||||
|
for (const key of Object.keys(headers)) {
|
||||||
|
const value = headers[key];
|
||||||
|
if (!Object.is(value, undefined) && !(0, _utils.isString)(value)) throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class RouteHandler {
|
||||||
|
constructor(baseURL, url, handler, times = Number.MAX_SAFE_INTEGER) {
|
||||||
|
this.handledCount = 0;
|
||||||
|
this._baseURL = void 0;
|
||||||
|
this._times = void 0;
|
||||||
|
this.url = void 0;
|
||||||
|
this.handler = void 0;
|
||||||
|
this._baseURL = baseURL;
|
||||||
|
this._times = times;
|
||||||
|
this.url = url;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
static prepareInterceptionPatterns(handlers) {
|
||||||
|
const patterns = [];
|
||||||
|
let all = false;
|
||||||
|
for (const handler of handlers) {
|
||||||
|
if ((0, _utils.isString)(handler.url)) patterns.push({
|
||||||
|
glob: handler.url
|
||||||
|
});else if ((0, _utils.isRegExp)(handler.url)) patterns.push({
|
||||||
|
regexSource: handler.url.source,
|
||||||
|
regexFlags: handler.url.flags
|
||||||
|
});else all = true;
|
||||||
|
}
|
||||||
|
if (all) return [{
|
||||||
|
glob: '**/*'
|
||||||
|
}];
|
||||||
|
return patterns;
|
||||||
|
}
|
||||||
|
matches(requestURL) {
|
||||||
|
return (0, _network.urlMatches)(this._baseURL, requestURL, this.url);
|
||||||
|
}
|
||||||
|
async handle(route) {
|
||||||
|
++this.handledCount;
|
||||||
|
const handledPromise = route._startHandling();
|
||||||
|
// Extract handler into a variable to avoid [RouteHandler.handler] in the stack.
|
||||||
|
const handler = this.handler;
|
||||||
|
const [handled] = await Promise.all([handledPromise, handler(route, route.request())]);
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
willExpire() {
|
||||||
|
return this.handledCount + 1 >= this._times;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.RouteHandler = RouteHandler;
|
||||||
|
class RawHeaders {
|
||||||
|
static _fromHeadersObjectLossy(headers) {
|
||||||
|
const headersArray = Object.entries(headers).map(([name, value]) => ({
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
})).filter(header => header.value !== undefined);
|
||||||
|
return new RawHeaders(headersArray);
|
||||||
|
}
|
||||||
|
constructor(headers) {
|
||||||
|
this._headersArray = void 0;
|
||||||
|
this._headersMap = new _multimap.MultiMap();
|
||||||
|
this._headersArray = headers;
|
||||||
|
for (const header of headers) this._headersMap.set(header.name.toLowerCase(), header.value);
|
||||||
|
}
|
||||||
|
get(name) {
|
||||||
|
const values = this.getAll(name);
|
||||||
|
if (!values || !values.length) return null;
|
||||||
|
return values.join(name.toLowerCase() === 'set-cookie' ? '\n' : ', ');
|
||||||
|
}
|
||||||
|
getAll(name) {
|
||||||
|
return [...this._headersMap.get(name.toLowerCase())];
|
||||||
|
}
|
||||||
|
headers() {
|
||||||
|
const result = {};
|
||||||
|
for (const name of this._headersMap.keys()) result[name] = this.get(name);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
headersArray() {
|
||||||
|
return this._headersArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.RawHeaders = RawHeaders;
|
@ -0,0 +1,655 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Page = exports.BindingCall = void 0;
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
var _path = _interopRequireDefault(require("path"));
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
var _network = require("../utils/network");
|
||||||
|
var _timeoutSettings = require("../common/timeoutSettings");
|
||||||
|
var _serializers = require("../protocol/serializers");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _fileUtils = require("../utils/fileUtils");
|
||||||
|
var _accessibility = require("./accessibility");
|
||||||
|
var _artifact = require("./artifact");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _clientHelper = require("./clientHelper");
|
||||||
|
var _coverage = require("./coverage");
|
||||||
|
var _download = require("./download");
|
||||||
|
var _elementHandle = require("./elementHandle");
|
||||||
|
var _events = require("./events");
|
||||||
|
var _fileChooser = require("./fileChooser");
|
||||||
|
var _frame = require("./frame");
|
||||||
|
var _input = require("./input");
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _network2 = require("./network");
|
||||||
|
var _video = require("./video");
|
||||||
|
var _waiter = require("./waiter");
|
||||||
|
var _worker = require("./worker");
|
||||||
|
var _harRouter = require("./harRouter");
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Page extends _channelOwner.ChannelOwner {
|
||||||
|
static from(page) {
|
||||||
|
return page._object;
|
||||||
|
}
|
||||||
|
static fromNullable(page) {
|
||||||
|
return page ? Page.from(page) : null;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._browserContext = void 0;
|
||||||
|
this._ownedContext = void 0;
|
||||||
|
this._mainFrame = void 0;
|
||||||
|
this._frames = new Set();
|
||||||
|
this._workers = new Set();
|
||||||
|
this._closed = false;
|
||||||
|
this._closedOrCrashedScope = new _utils.LongStandingScope();
|
||||||
|
this._viewportSize = void 0;
|
||||||
|
this._routes = [];
|
||||||
|
this.accessibility = void 0;
|
||||||
|
this.coverage = void 0;
|
||||||
|
this.keyboard = void 0;
|
||||||
|
this.mouse = void 0;
|
||||||
|
this.request = void 0;
|
||||||
|
this.touchscreen = void 0;
|
||||||
|
this._bindings = new Map();
|
||||||
|
this._timeoutSettings = void 0;
|
||||||
|
this._video = null;
|
||||||
|
this._opener = void 0;
|
||||||
|
this._browserContext = parent;
|
||||||
|
this._timeoutSettings = new _timeoutSettings.TimeoutSettings(this._browserContext._timeoutSettings);
|
||||||
|
this.accessibility = new _accessibility.Accessibility(this._channel);
|
||||||
|
this.keyboard = new _input.Keyboard(this);
|
||||||
|
this.mouse = new _input.Mouse(this);
|
||||||
|
this.request = this._browserContext.request;
|
||||||
|
this.touchscreen = new _input.Touchscreen(this);
|
||||||
|
this._mainFrame = _frame.Frame.from(initializer.mainFrame);
|
||||||
|
this._mainFrame._page = this;
|
||||||
|
this._frames.add(this._mainFrame);
|
||||||
|
this._viewportSize = initializer.viewportSize || null;
|
||||||
|
this._closed = initializer.isClosed;
|
||||||
|
this._opener = Page.fromNullable(initializer.opener);
|
||||||
|
this._channel.on('bindingCall', ({
|
||||||
|
binding
|
||||||
|
}) => this._onBinding(BindingCall.from(binding)));
|
||||||
|
this._channel.on('close', () => this._onClose());
|
||||||
|
this._channel.on('crash', () => this._onCrash());
|
||||||
|
this._channel.on('download', ({
|
||||||
|
url,
|
||||||
|
suggestedFilename,
|
||||||
|
artifact
|
||||||
|
}) => {
|
||||||
|
const artifactObject = _artifact.Artifact.from(artifact);
|
||||||
|
this.emit(_events.Events.Page.Download, new _download.Download(this, url, suggestedFilename, artifactObject));
|
||||||
|
});
|
||||||
|
this._channel.on('fileChooser', ({
|
||||||
|
element,
|
||||||
|
isMultiple
|
||||||
|
}) => this.emit(_events.Events.Page.FileChooser, new _fileChooser.FileChooser(this, _elementHandle.ElementHandle.from(element), isMultiple)));
|
||||||
|
this._channel.on('frameAttached', ({
|
||||||
|
frame
|
||||||
|
}) => this._onFrameAttached(_frame.Frame.from(frame)));
|
||||||
|
this._channel.on('frameDetached', ({
|
||||||
|
frame
|
||||||
|
}) => this._onFrameDetached(_frame.Frame.from(frame)));
|
||||||
|
this._channel.on('pageError', ({
|
||||||
|
error
|
||||||
|
}) => this.emit(_events.Events.Page.PageError, (0, _serializers.parseError)(error)));
|
||||||
|
this._channel.on('route', ({
|
||||||
|
route
|
||||||
|
}) => this._onRoute(_network2.Route.from(route)));
|
||||||
|
this._channel.on('video', ({
|
||||||
|
artifact
|
||||||
|
}) => {
|
||||||
|
const artifactObject = _artifact.Artifact.from(artifact);
|
||||||
|
this._forceVideo()._artifactReady(artifactObject);
|
||||||
|
});
|
||||||
|
this._channel.on('webSocket', ({
|
||||||
|
webSocket
|
||||||
|
}) => this.emit(_events.Events.Page.WebSocket, _network2.WebSocket.from(webSocket)));
|
||||||
|
this._channel.on('worker', ({
|
||||||
|
worker
|
||||||
|
}) => this._onWorker(_worker.Worker.from(worker)));
|
||||||
|
this.coverage = new _coverage.Coverage(this._channel);
|
||||||
|
this.once(_events.Events.Page.Close, () => this._closedOrCrashedScope.close(_errors.kBrowserOrContextClosedError));
|
||||||
|
this.once(_events.Events.Page.Crash, () => this._closedOrCrashedScope.close(_errors.kBrowserOrContextClosedError));
|
||||||
|
this._setEventToSubscriptionMapping(new Map([[_events.Events.Page.Console, 'console'], [_events.Events.Page.Dialog, 'dialog'], [_events.Events.Page.Request, 'request'], [_events.Events.Page.Response, 'response'], [_events.Events.Page.RequestFinished, 'requestFinished'], [_events.Events.Page.RequestFailed, 'requestFailed'], [_events.Events.Page.FileChooser, 'fileChooser']]));
|
||||||
|
}
|
||||||
|
_onFrameAttached(frame) {
|
||||||
|
frame._page = this;
|
||||||
|
this._frames.add(frame);
|
||||||
|
if (frame._parentFrame) frame._parentFrame._childFrames.add(frame);
|
||||||
|
this.emit(_events.Events.Page.FrameAttached, frame);
|
||||||
|
}
|
||||||
|
_onFrameDetached(frame) {
|
||||||
|
this._frames.delete(frame);
|
||||||
|
frame._detached = true;
|
||||||
|
if (frame._parentFrame) frame._parentFrame._childFrames.delete(frame);
|
||||||
|
this.emit(_events.Events.Page.FrameDetached, frame);
|
||||||
|
}
|
||||||
|
async _onRoute(route) {
|
||||||
|
const routeHandlers = this._routes.slice();
|
||||||
|
for (const routeHandler of routeHandlers) {
|
||||||
|
if (!routeHandler.matches(route.request().url())) continue;
|
||||||
|
if (routeHandler.willExpire()) this._routes.splice(this._routes.indexOf(routeHandler), 1);
|
||||||
|
const handled = await routeHandler.handle(route);
|
||||||
|
if (!this._routes.length) this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {});
|
||||||
|
if (handled) return;
|
||||||
|
}
|
||||||
|
await this._browserContext._onRoute(route);
|
||||||
|
}
|
||||||
|
async _onBinding(bindingCall) {
|
||||||
|
const func = this._bindings.get(bindingCall._initializer.name);
|
||||||
|
if (func) {
|
||||||
|
await bindingCall.call(func);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this._browserContext._onBinding(bindingCall);
|
||||||
|
}
|
||||||
|
_onWorker(worker) {
|
||||||
|
this._workers.add(worker);
|
||||||
|
worker._page = this;
|
||||||
|
this.emit(_events.Events.Page.Worker, worker);
|
||||||
|
}
|
||||||
|
_onClose() {
|
||||||
|
this._closed = true;
|
||||||
|
this._browserContext._pages.delete(this);
|
||||||
|
this._browserContext._backgroundPages.delete(this);
|
||||||
|
this.emit(_events.Events.Page.Close, this);
|
||||||
|
}
|
||||||
|
_onCrash() {
|
||||||
|
this.emit(_events.Events.Page.Crash, this);
|
||||||
|
}
|
||||||
|
context() {
|
||||||
|
return this._browserContext;
|
||||||
|
}
|
||||||
|
async opener() {
|
||||||
|
if (!this._opener || this._opener.isClosed()) return null;
|
||||||
|
return this._opener;
|
||||||
|
}
|
||||||
|
mainFrame() {
|
||||||
|
return this._mainFrame;
|
||||||
|
}
|
||||||
|
frame(frameSelector) {
|
||||||
|
const name = (0, _utils.isString)(frameSelector) ? frameSelector : frameSelector.name;
|
||||||
|
const url = (0, _utils.isObject)(frameSelector) ? frameSelector.url : undefined;
|
||||||
|
(0, _utils.assert)(name || url, 'Either name or url matcher should be specified');
|
||||||
|
return this.frames().find(f => {
|
||||||
|
if (name) return f.name() === name;
|
||||||
|
return (0, _network.urlMatches)(this._browserContext._options.baseURL, f.url(), url);
|
||||||
|
}) || null;
|
||||||
|
}
|
||||||
|
frames() {
|
||||||
|
return [...this._frames];
|
||||||
|
}
|
||||||
|
setDefaultNavigationTimeout(timeout) {
|
||||||
|
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||||
|
this._wrapApiCall(async () => {
|
||||||
|
this._channel.setDefaultNavigationTimeoutNoReply({
|
||||||
|
timeout
|
||||||
|
}).catch(() => {});
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
setDefaultTimeout(timeout) {
|
||||||
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
|
this._wrapApiCall(async () => {
|
||||||
|
this._channel.setDefaultTimeoutNoReply({
|
||||||
|
timeout
|
||||||
|
}).catch(() => {});
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
_forceVideo() {
|
||||||
|
if (!this._video) this._video = new _video.Video(this, this._connection);
|
||||||
|
return this._video;
|
||||||
|
}
|
||||||
|
video() {
|
||||||
|
// Note: we are creating Video object lazily, because we do not know
|
||||||
|
// BrowserContextOptions when constructing the page - it is assigned
|
||||||
|
// too late during launchPersistentContext.
|
||||||
|
if (!this._browserContext._options.recordVideo) return null;
|
||||||
|
return this._forceVideo();
|
||||||
|
}
|
||||||
|
async $(selector, options) {
|
||||||
|
return this._mainFrame.$(selector, options);
|
||||||
|
}
|
||||||
|
async waitForSelector(selector, options) {
|
||||||
|
return this._mainFrame.waitForSelector(selector, options);
|
||||||
|
}
|
||||||
|
async dispatchEvent(selector, type, eventInit, options) {
|
||||||
|
return this._mainFrame.dispatchEvent(selector, type, eventInit, options);
|
||||||
|
}
|
||||||
|
async evaluateHandle(pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
|
||||||
|
return this._mainFrame.evaluateHandle(pageFunction, arg);
|
||||||
|
}
|
||||||
|
async $eval(selector, pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
|
||||||
|
return this._mainFrame.$eval(selector, pageFunction, arg);
|
||||||
|
}
|
||||||
|
async $$eval(selector, pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
|
||||||
|
return this._mainFrame.$$eval(selector, pageFunction, arg);
|
||||||
|
}
|
||||||
|
async $$(selector) {
|
||||||
|
return this._mainFrame.$$(selector);
|
||||||
|
}
|
||||||
|
async addScriptTag(options = {}) {
|
||||||
|
return this._mainFrame.addScriptTag(options);
|
||||||
|
}
|
||||||
|
async addStyleTag(options = {}) {
|
||||||
|
return this._mainFrame.addStyleTag(options);
|
||||||
|
}
|
||||||
|
async exposeFunction(name, callback) {
|
||||||
|
await this._channel.exposeBinding({
|
||||||
|
name
|
||||||
|
});
|
||||||
|
const binding = (source, ...args) => callback(...args);
|
||||||
|
this._bindings.set(name, binding);
|
||||||
|
}
|
||||||
|
async exposeBinding(name, callback, options = {}) {
|
||||||
|
await this._channel.exposeBinding({
|
||||||
|
name,
|
||||||
|
needsHandle: options.handle
|
||||||
|
});
|
||||||
|
this._bindings.set(name, callback);
|
||||||
|
}
|
||||||
|
async setExtraHTTPHeaders(headers) {
|
||||||
|
(0, _network2.validateHeaders)(headers);
|
||||||
|
await this._channel.setExtraHTTPHeaders({
|
||||||
|
headers: (0, _utils.headersObjectToArray)(headers)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
url() {
|
||||||
|
return this._mainFrame.url();
|
||||||
|
}
|
||||||
|
async content() {
|
||||||
|
return this._mainFrame.content();
|
||||||
|
}
|
||||||
|
async setContent(html, options) {
|
||||||
|
return this._mainFrame.setContent(html, options);
|
||||||
|
}
|
||||||
|
async goto(url, options) {
|
||||||
|
return this._mainFrame.goto(url, options);
|
||||||
|
}
|
||||||
|
async reload(options = {}) {
|
||||||
|
const waitUntil = (0, _frame.verifyLoadState)('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
|
return _network2.Response.fromNullable((await this._channel.reload({
|
||||||
|
...options,
|
||||||
|
waitUntil
|
||||||
|
})).response);
|
||||||
|
}
|
||||||
|
async waitForLoadState(state, options) {
|
||||||
|
return this._mainFrame.waitForLoadState(state, options);
|
||||||
|
}
|
||||||
|
async waitForNavigation(options) {
|
||||||
|
return this._mainFrame.waitForNavigation(options);
|
||||||
|
}
|
||||||
|
async waitForURL(url, options) {
|
||||||
|
return this._mainFrame.waitForURL(url, options);
|
||||||
|
}
|
||||||
|
async waitForRequest(urlOrPredicate, options = {}) {
|
||||||
|
const predicate = request => {
|
||||||
|
if ((0, _utils.isString)(urlOrPredicate) || (0, _utils.isRegExp)(urlOrPredicate)) return (0, _network.urlMatches)(this._browserContext._options.baseURL, request.url(), urlOrPredicate);
|
||||||
|
return urlOrPredicate(request);
|
||||||
|
};
|
||||||
|
const trimmedUrl = trimUrl(urlOrPredicate);
|
||||||
|
const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : undefined;
|
||||||
|
return this._waitForEvent(_events.Events.Page.Request, {
|
||||||
|
predicate,
|
||||||
|
timeout: options.timeout
|
||||||
|
}, logLine);
|
||||||
|
}
|
||||||
|
async waitForResponse(urlOrPredicate, options = {}) {
|
||||||
|
const predicate = response => {
|
||||||
|
if ((0, _utils.isString)(urlOrPredicate) || (0, _utils.isRegExp)(urlOrPredicate)) return (0, _network.urlMatches)(this._browserContext._options.baseURL, response.url(), urlOrPredicate);
|
||||||
|
return urlOrPredicate(response);
|
||||||
|
};
|
||||||
|
const trimmedUrl = trimUrl(urlOrPredicate);
|
||||||
|
const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : undefined;
|
||||||
|
return this._waitForEvent(_events.Events.Page.Response, {
|
||||||
|
predicate,
|
||||||
|
timeout: options.timeout
|
||||||
|
}, logLine);
|
||||||
|
}
|
||||||
|
async waitForEvent(event, optionsOrPredicate = {}) {
|
||||||
|
return this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`);
|
||||||
|
}
|
||||||
|
async _waitForEvent(event, optionsOrPredicate, logLine) {
|
||||||
|
return this._wrapApiCall(async () => {
|
||||||
|
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
|
const waiter = _waiter.Waiter.createForEvent(this, event);
|
||||||
|
if (logLine) waiter.log(logLine);
|
||||||
|
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
|
||||||
|
if (event !== _events.Events.Page.Crash) waiter.rejectOnEvent(this, _events.Events.Page.Crash, new Error('Page crashed'));
|
||||||
|
if (event !== _events.Events.Page.Close) waiter.rejectOnEvent(this, _events.Events.Page.Close, new Error('Page closed'));
|
||||||
|
const result = await waiter.waitForEvent(this, event, predicate);
|
||||||
|
waiter.dispose();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async goBack(options = {}) {
|
||||||
|
const waitUntil = (0, _frame.verifyLoadState)('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
|
return _network2.Response.fromNullable((await this._channel.goBack({
|
||||||
|
...options,
|
||||||
|
waitUntil
|
||||||
|
})).response);
|
||||||
|
}
|
||||||
|
async goForward(options = {}) {
|
||||||
|
const waitUntil = (0, _frame.verifyLoadState)('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
|
return _network2.Response.fromNullable((await this._channel.goForward({
|
||||||
|
...options,
|
||||||
|
waitUntil
|
||||||
|
})).response);
|
||||||
|
}
|
||||||
|
async emulateMedia(options = {}) {
|
||||||
|
await this._channel.emulateMedia({
|
||||||
|
media: options.media === null ? 'no-override' : options.media,
|
||||||
|
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
|
||||||
|
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
|
||||||
|
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async setViewportSize(viewportSize) {
|
||||||
|
this._viewportSize = viewportSize;
|
||||||
|
await this._channel.setViewportSize({
|
||||||
|
viewportSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
viewportSize() {
|
||||||
|
return this._viewportSize;
|
||||||
|
}
|
||||||
|
async evaluate(pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
|
||||||
|
return this._mainFrame.evaluate(pageFunction, arg);
|
||||||
|
}
|
||||||
|
async addInitScript(script, arg) {
|
||||||
|
const source = await (0, _clientHelper.evaluationScript)(script, arg);
|
||||||
|
await this._channel.addInitScript({
|
||||||
|
source
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async route(url, handler, options = {}) {
|
||||||
|
this._routes.unshift(new _network2.RouteHandler(this._browserContext._options.baseURL, url, handler, options.times));
|
||||||
|
await this._updateInterceptionPatterns();
|
||||||
|
}
|
||||||
|
async routeFromHAR(har, options = {}) {
|
||||||
|
if (options.update) {
|
||||||
|
await this._browserContext._recordIntoHAR(har, this, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const harRouter = await _harRouter.HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', {
|
||||||
|
urlMatch: options.url
|
||||||
|
});
|
||||||
|
harRouter.addPageRoute(this);
|
||||||
|
}
|
||||||
|
async unroute(url, handler) {
|
||||||
|
this._routes = this._routes.filter(route => !(0, _utils.urlMatchesEqual)(route.url, url) || handler && route.handler !== handler);
|
||||||
|
await this._updateInterceptionPatterns();
|
||||||
|
}
|
||||||
|
async _updateInterceptionPatterns() {
|
||||||
|
const patterns = _network2.RouteHandler.prepareInterceptionPatterns(this._routes);
|
||||||
|
await this._channel.setNetworkInterceptionPatterns({
|
||||||
|
patterns
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
const copy = {
|
||||||
|
...options,
|
||||||
|
mask: undefined
|
||||||
|
};
|
||||||
|
if (!copy.type) copy.type = (0, _elementHandle.determineScreenshotType)(options);
|
||||||
|
if (options.mask) {
|
||||||
|
copy.mask = options.mask.map(locator => ({
|
||||||
|
frame: locator._frame._channel,
|
||||||
|
selector: locator._selector
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const result = await this._channel.screenshot(copy);
|
||||||
|
if (options.path) {
|
||||||
|
await (0, _fileUtils.mkdirIfNeeded)(options.path);
|
||||||
|
await _fs.default.promises.writeFile(options.path, result.binary);
|
||||||
|
}
|
||||||
|
return result.binary;
|
||||||
|
}
|
||||||
|
async _expectScreenshot(options) {
|
||||||
|
var _options$screenshotOp, _options$screenshotOp2;
|
||||||
|
const mask = (_options$screenshotOp = options.screenshotOptions) !== null && _options$screenshotOp !== void 0 && _options$screenshotOp.mask ? (_options$screenshotOp2 = options.screenshotOptions) === null || _options$screenshotOp2 === void 0 ? void 0 : _options$screenshotOp2.mask.map(locator => ({
|
||||||
|
frame: locator._frame._channel,
|
||||||
|
selector: locator._selector
|
||||||
|
})) : undefined;
|
||||||
|
const locator = options.locator ? {
|
||||||
|
frame: options.locator._frame._channel,
|
||||||
|
selector: options.locator._selector
|
||||||
|
} : undefined;
|
||||||
|
return await this._channel.expectScreenshot({
|
||||||
|
...options,
|
||||||
|
isNot: !!options.isNot,
|
||||||
|
locator,
|
||||||
|
screenshotOptions: {
|
||||||
|
...options.screenshotOptions,
|
||||||
|
mask
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async title() {
|
||||||
|
return this._mainFrame.title();
|
||||||
|
}
|
||||||
|
async bringToFront() {
|
||||||
|
await this._channel.bringToFront();
|
||||||
|
}
|
||||||
|
async close(options = {
|
||||||
|
runBeforeUnload: undefined
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
if (this._ownedContext) await this._ownedContext.close();else await this._channel.close(options);
|
||||||
|
} catch (e) {
|
||||||
|
if ((0, _errors.isSafeCloseError)(e) && !options.runBeforeUnload) return;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isClosed() {
|
||||||
|
return this._closed;
|
||||||
|
}
|
||||||
|
async click(selector, options) {
|
||||||
|
return this._mainFrame.click(selector, options);
|
||||||
|
}
|
||||||
|
async dragAndDrop(source, target, options) {
|
||||||
|
return this._mainFrame.dragAndDrop(source, target, options);
|
||||||
|
}
|
||||||
|
async dblclick(selector, options) {
|
||||||
|
return this._mainFrame.dblclick(selector, options);
|
||||||
|
}
|
||||||
|
async tap(selector, options) {
|
||||||
|
return this._mainFrame.tap(selector, options);
|
||||||
|
}
|
||||||
|
async fill(selector, value, options) {
|
||||||
|
return this._mainFrame.fill(selector, value, options);
|
||||||
|
}
|
||||||
|
locator(selector, options) {
|
||||||
|
return this.mainFrame().locator(selector, options);
|
||||||
|
}
|
||||||
|
getByTestId(testId) {
|
||||||
|
return this.mainFrame().getByTestId(testId);
|
||||||
|
}
|
||||||
|
getByAltText(text, options) {
|
||||||
|
return this.mainFrame().getByAltText(text, options);
|
||||||
|
}
|
||||||
|
getByLabel(text, options) {
|
||||||
|
return this.mainFrame().getByLabel(text, options);
|
||||||
|
}
|
||||||
|
getByPlaceholder(text, options) {
|
||||||
|
return this.mainFrame().getByPlaceholder(text, options);
|
||||||
|
}
|
||||||
|
getByText(text, options) {
|
||||||
|
return this.mainFrame().getByText(text, options);
|
||||||
|
}
|
||||||
|
getByTitle(text, options) {
|
||||||
|
return this.mainFrame().getByTitle(text, options);
|
||||||
|
}
|
||||||
|
getByRole(role, options = {}) {
|
||||||
|
return this.mainFrame().getByRole(role, options);
|
||||||
|
}
|
||||||
|
frameLocator(selector) {
|
||||||
|
return this.mainFrame().frameLocator(selector);
|
||||||
|
}
|
||||||
|
async focus(selector, options) {
|
||||||
|
return this._mainFrame.focus(selector, options);
|
||||||
|
}
|
||||||
|
async textContent(selector, options) {
|
||||||
|
return this._mainFrame.textContent(selector, options);
|
||||||
|
}
|
||||||
|
async innerText(selector, options) {
|
||||||
|
return this._mainFrame.innerText(selector, options);
|
||||||
|
}
|
||||||
|
async innerHTML(selector, options) {
|
||||||
|
return this._mainFrame.innerHTML(selector, options);
|
||||||
|
}
|
||||||
|
async getAttribute(selector, name, options) {
|
||||||
|
return this._mainFrame.getAttribute(selector, name, options);
|
||||||
|
}
|
||||||
|
async inputValue(selector, options) {
|
||||||
|
return this._mainFrame.inputValue(selector, options);
|
||||||
|
}
|
||||||
|
async isChecked(selector, options) {
|
||||||
|
return this._mainFrame.isChecked(selector, options);
|
||||||
|
}
|
||||||
|
async isDisabled(selector, options) {
|
||||||
|
return this._mainFrame.isDisabled(selector, options);
|
||||||
|
}
|
||||||
|
async isEditable(selector, options) {
|
||||||
|
return this._mainFrame.isEditable(selector, options);
|
||||||
|
}
|
||||||
|
async isEnabled(selector, options) {
|
||||||
|
return this._mainFrame.isEnabled(selector, options);
|
||||||
|
}
|
||||||
|
async isHidden(selector, options) {
|
||||||
|
return this._mainFrame.isHidden(selector, options);
|
||||||
|
}
|
||||||
|
async isVisible(selector, options) {
|
||||||
|
return this._mainFrame.isVisible(selector, options);
|
||||||
|
}
|
||||||
|
async hover(selector, options) {
|
||||||
|
return this._mainFrame.hover(selector, options);
|
||||||
|
}
|
||||||
|
async selectOption(selector, values, options) {
|
||||||
|
return this._mainFrame.selectOption(selector, values, options);
|
||||||
|
}
|
||||||
|
async setInputFiles(selector, files, options) {
|
||||||
|
return this._mainFrame.setInputFiles(selector, files, options);
|
||||||
|
}
|
||||||
|
async type(selector, text, options) {
|
||||||
|
return this._mainFrame.type(selector, text, options);
|
||||||
|
}
|
||||||
|
async press(selector, key, options) {
|
||||||
|
return this._mainFrame.press(selector, key, options);
|
||||||
|
}
|
||||||
|
async check(selector, options) {
|
||||||
|
return this._mainFrame.check(selector, options);
|
||||||
|
}
|
||||||
|
async uncheck(selector, options) {
|
||||||
|
return this._mainFrame.uncheck(selector, options);
|
||||||
|
}
|
||||||
|
async setChecked(selector, checked, options) {
|
||||||
|
return this._mainFrame.setChecked(selector, checked, options);
|
||||||
|
}
|
||||||
|
async waitForTimeout(timeout) {
|
||||||
|
return this._mainFrame.waitForTimeout(timeout);
|
||||||
|
}
|
||||||
|
async waitForFunction(pageFunction, arg, options) {
|
||||||
|
return this._mainFrame.waitForFunction(pageFunction, arg, options);
|
||||||
|
}
|
||||||
|
workers() {
|
||||||
|
return [...this._workers];
|
||||||
|
}
|
||||||
|
async pause() {
|
||||||
|
var _this$_instrumentatio;
|
||||||
|
if (require('inspector').url()) return;
|
||||||
|
const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout();
|
||||||
|
const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout();
|
||||||
|
this._browserContext.setDefaultNavigationTimeout(0);
|
||||||
|
this._browserContext.setDefaultTimeout(0);
|
||||||
|
(_this$_instrumentatio = this._instrumentation) === null || _this$_instrumentatio === void 0 ? void 0 : _this$_instrumentatio.onWillPause();
|
||||||
|
await this._closedOrCrashedScope.safeRace(this.context()._channel.pause());
|
||||||
|
this._browserContext.setDefaultNavigationTimeout(defaultNavigationTimeout);
|
||||||
|
this._browserContext.setDefaultTimeout(defaultTimeout);
|
||||||
|
}
|
||||||
|
async pdf(options = {}) {
|
||||||
|
const transportOptions = {
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
if (transportOptions.margin) transportOptions.margin = {
|
||||||
|
...transportOptions.margin
|
||||||
|
};
|
||||||
|
if (typeof options.width === 'number') transportOptions.width = options.width + 'px';
|
||||||
|
if (typeof options.height === 'number') transportOptions.height = options.height + 'px';
|
||||||
|
for (const margin of ['top', 'right', 'bottom', 'left']) {
|
||||||
|
const index = margin;
|
||||||
|
if (options.margin && typeof options.margin[index] === 'number') transportOptions.margin[index] = transportOptions.margin[index] + 'px';
|
||||||
|
}
|
||||||
|
const result = await this._channel.pdf(transportOptions);
|
||||||
|
if (options.path) {
|
||||||
|
await _fs.default.promises.mkdir(_path.default.dirname(options.path), {
|
||||||
|
recursive: true
|
||||||
|
});
|
||||||
|
await _fs.default.promises.writeFile(options.path, result.pdf);
|
||||||
|
}
|
||||||
|
return result.pdf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Page = Page;
|
||||||
|
class BindingCall extends _channelOwner.ChannelOwner {
|
||||||
|
static from(channel) {
|
||||||
|
return channel._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
}
|
||||||
|
async call(func) {
|
||||||
|
try {
|
||||||
|
const frame = _frame.Frame.from(this._initializer.frame);
|
||||||
|
const source = {
|
||||||
|
context: frame._page.context(),
|
||||||
|
page: frame._page,
|
||||||
|
frame
|
||||||
|
};
|
||||||
|
let result;
|
||||||
|
if (this._initializer.handle) result = await func(source, _jsHandle.JSHandle.from(this._initializer.handle));else result = await func(source, ...this._initializer.args.map(_jsHandle.parseResult));
|
||||||
|
this._channel.resolve({
|
||||||
|
result: (0, _jsHandle.serializeArgument)(result)
|
||||||
|
}).catch(() => {});
|
||||||
|
} catch (e) {
|
||||||
|
this._channel.reject({
|
||||||
|
error: (0, _serializers.serializeError)(e)
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.BindingCall = BindingCall;
|
||||||
|
function trimEnd(s) {
|
||||||
|
if (s.length > 50) s = s.substring(0, 50) + '\u2026';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
function trimUrl(param) {
|
||||||
|
if ((0, _utils.isRegExp)(param)) return `/${trimEnd(param.source)}/${param.flags}`;
|
||||||
|
if ((0, _utils.isString)(param)) return `"${trimEnd(param)}"`;
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Playwright = void 0;
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
var _android = require("./android");
|
||||||
|
var _browserType = require("./browserType");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _electron = require("./electron");
|
||||||
|
var _fetch = require("./fetch");
|
||||||
|
var _selectors = require("./selectors");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Playwright extends _channelOwner.ChannelOwner {
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._android = void 0;
|
||||||
|
this._electron = void 0;
|
||||||
|
this.chromium = void 0;
|
||||||
|
this.firefox = void 0;
|
||||||
|
this.webkit = void 0;
|
||||||
|
this.devices = void 0;
|
||||||
|
this.selectors = void 0;
|
||||||
|
this.request = void 0;
|
||||||
|
this.errors = void 0;
|
||||||
|
this.request = new _fetch.APIRequest(this);
|
||||||
|
this.chromium = _browserType.BrowserType.from(initializer.chromium);
|
||||||
|
this.chromium._playwright = this;
|
||||||
|
this.firefox = _browserType.BrowserType.from(initializer.firefox);
|
||||||
|
this.firefox._playwright = this;
|
||||||
|
this.webkit = _browserType.BrowserType.from(initializer.webkit);
|
||||||
|
this.webkit._playwright = this;
|
||||||
|
this._android = _android.Android.from(initializer.android);
|
||||||
|
this._electron = _electron.Electron.from(initializer.electron);
|
||||||
|
this.devices = {};
|
||||||
|
for (const {
|
||||||
|
name,
|
||||||
|
descriptor
|
||||||
|
} of initializer.deviceDescriptors) this.devices[name] = descriptor;
|
||||||
|
this.selectors = new _selectors.Selectors();
|
||||||
|
this.errors = {
|
||||||
|
TimeoutError: _errors.TimeoutError
|
||||||
|
};
|
||||||
|
const selectorsOwner = _selectors.SelectorsOwner.from(initializer.selectors);
|
||||||
|
this.selectors._addChannel(selectorsOwner);
|
||||||
|
this._connection.on('close', () => {
|
||||||
|
this.selectors._removeChannel(selectorsOwner);
|
||||||
|
});
|
||||||
|
global._playwrightInstance = this;
|
||||||
|
}
|
||||||
|
_setSelectors(selectors) {
|
||||||
|
const selectorsOwner = _selectors.SelectorsOwner.from(this._initializer.selectors);
|
||||||
|
this.selectors._removeChannel(selectorsOwner);
|
||||||
|
this.selectors = selectors;
|
||||||
|
this.selectors._addChannel(selectorsOwner);
|
||||||
|
}
|
||||||
|
static from(channel) {
|
||||||
|
return channel._object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Playwright = Playwright;
|
@ -0,0 +1,67 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.SelectorsOwner = exports.Selectors = void 0;
|
||||||
|
var _clientHelper = require("./clientHelper");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _locator = require("./locator");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Selectors {
|
||||||
|
constructor() {
|
||||||
|
this._channels = new Set();
|
||||||
|
this._registrations = [];
|
||||||
|
}
|
||||||
|
async register(name, script, options = {}) {
|
||||||
|
const source = await (0, _clientHelper.evaluationScript)(script, undefined, false);
|
||||||
|
const params = {
|
||||||
|
...options,
|
||||||
|
name,
|
||||||
|
source
|
||||||
|
};
|
||||||
|
for (const channel of this._channels) await channel._channel.register(params);
|
||||||
|
this._registrations.push(params);
|
||||||
|
}
|
||||||
|
setTestIdAttribute(attributeName) {
|
||||||
|
(0, _locator.setTestIdAttribute)(attributeName);
|
||||||
|
for (const channel of this._channels) channel._channel.setTestIdAttributeName({
|
||||||
|
testIdAttributeName: attributeName
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
_addChannel(channel) {
|
||||||
|
this._channels.add(channel);
|
||||||
|
for (const params of this._registrations) {
|
||||||
|
// This should not fail except for connection closure, but just in case we catch.
|
||||||
|
channel._channel.register(params).catch(() => {});
|
||||||
|
channel._channel.setTestIdAttributeName({
|
||||||
|
testIdAttributeName: (0, _locator.testIdAttributeName)()
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_removeChannel(channel) {
|
||||||
|
this._channels.delete(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Selectors = Selectors;
|
||||||
|
class SelectorsOwner extends _channelOwner.ChannelOwner {
|
||||||
|
static from(browser) {
|
||||||
|
return browser._object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.SelectorsOwner = SelectorsOwner;
|
@ -0,0 +1,54 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Stream = void 0;
|
||||||
|
var _stream = require("stream");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Stream extends _channelOwner.ChannelOwner {
|
||||||
|
static from(Stream) {
|
||||||
|
return Stream._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
}
|
||||||
|
stream() {
|
||||||
|
return new StreamImpl(this._channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Stream = Stream;
|
||||||
|
class StreamImpl extends _stream.Readable {
|
||||||
|
constructor(channel) {
|
||||||
|
super();
|
||||||
|
this._channel = void 0;
|
||||||
|
this._channel = channel;
|
||||||
|
}
|
||||||
|
async _read() {
|
||||||
|
const result = await this._channel.read({
|
||||||
|
size: 1024 * 1024
|
||||||
|
});
|
||||||
|
if (result.binary.byteLength) this.push(result.binary);else this.push(null);
|
||||||
|
}
|
||||||
|
_destroy(error, callback) {
|
||||||
|
// Stream might be destroyed after the connection was closed.
|
||||||
|
this._channel.close().catch(e => null);
|
||||||
|
super._destroy(error, callback);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Tracing = void 0;
|
||||||
|
var _artifact = require("./artifact");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Tracing extends _channelOwner.ChannelOwner {
|
||||||
|
static from(channel) {
|
||||||
|
return channel._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._includeSources = false;
|
||||||
|
this._tracesDir = void 0;
|
||||||
|
this._stacksId = void 0;
|
||||||
|
this._isTracing = false;
|
||||||
|
}
|
||||||
|
async start(options = {}) {
|
||||||
|
this._includeSources = !!options.sources;
|
||||||
|
const traceName = await this._wrapApiCall(async () => {
|
||||||
|
await this._channel.tracingStart(options);
|
||||||
|
const response = await this._channel.tracingStartChunk({
|
||||||
|
name: options.name,
|
||||||
|
title: options.title
|
||||||
|
});
|
||||||
|
return response.traceName;
|
||||||
|
});
|
||||||
|
await this._startCollectingStacks(traceName);
|
||||||
|
}
|
||||||
|
async startChunk(options = {}) {
|
||||||
|
const {
|
||||||
|
traceName
|
||||||
|
} = await this._channel.tracingStartChunk(options);
|
||||||
|
await this._startCollectingStacks(traceName);
|
||||||
|
}
|
||||||
|
async _startCollectingStacks(traceName) {
|
||||||
|
if (!this._isTracing) {
|
||||||
|
this._isTracing = true;
|
||||||
|
this._connection.setIsTracing(true);
|
||||||
|
}
|
||||||
|
const result = await this._connection.localUtils()._channel.tracingStarted({
|
||||||
|
tracesDir: this._tracesDir,
|
||||||
|
traceName
|
||||||
|
});
|
||||||
|
this._stacksId = result.stacksId;
|
||||||
|
}
|
||||||
|
async stopChunk(options = {}) {
|
||||||
|
await this._doStopChunk(options.path);
|
||||||
|
}
|
||||||
|
async stop(options = {}) {
|
||||||
|
await this._wrapApiCall(async () => {
|
||||||
|
await this._doStopChunk(options.path);
|
||||||
|
await this._channel.tracingStop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _doStopChunk(filePath) {
|
||||||
|
if (this._isTracing) {
|
||||||
|
this._isTracing = false;
|
||||||
|
this._connection.setIsTracing(false);
|
||||||
|
}
|
||||||
|
if (!filePath) {
|
||||||
|
// Not interested in artifacts.
|
||||||
|
await this._channel.tracingStopChunk({
|
||||||
|
mode: 'discard'
|
||||||
|
});
|
||||||
|
if (this._stacksId) await this._connection.localUtils()._channel.traceDiscarded({
|
||||||
|
stacksId: this._stacksId
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isLocal = !this._connection.isRemote();
|
||||||
|
if (isLocal) {
|
||||||
|
const result = await this._channel.tracingStopChunk({
|
||||||
|
mode: 'entries'
|
||||||
|
});
|
||||||
|
await this._connection.localUtils()._channel.zip({
|
||||||
|
zipFile: filePath,
|
||||||
|
entries: result.entries,
|
||||||
|
mode: 'write',
|
||||||
|
stacksId: this._stacksId,
|
||||||
|
includeSources: this._includeSources
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await this._channel.tracingStopChunk({
|
||||||
|
mode: 'archive'
|
||||||
|
});
|
||||||
|
|
||||||
|
// The artifact may be missing if the browser closed while stopping tracing.
|
||||||
|
if (!result.artifact) {
|
||||||
|
if (this._stacksId) await this._connection.localUtils()._channel.traceDiscarded({
|
||||||
|
stacksId: this._stacksId
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save trace to the final local file.
|
||||||
|
const artifact = _artifact.Artifact.from(result.artifact);
|
||||||
|
await artifact.saveAs(filePath);
|
||||||
|
await artifact.delete();
|
||||||
|
await this._connection.localUtils()._channel.zip({
|
||||||
|
zipFile: filePath,
|
||||||
|
entries: [],
|
||||||
|
mode: 'append',
|
||||||
|
stacksId: this._stacksId,
|
||||||
|
includeSources: this._includeSources
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Tracing = Tracing;
|
@ -0,0 +1,25 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.kLifecycleEvents = void 0;
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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 kLifecycleEvents = new Set(['load', 'domcontentloaded', 'networkidle', 'commit']);
|
||||||
|
exports.kLifecycleEvents = kLifecycleEvents;
|
@ -0,0 +1,51 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Video = void 0;
|
||||||
|
var _utils = require("../utils");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Video {
|
||||||
|
constructor(page, connection) {
|
||||||
|
this._artifact = null;
|
||||||
|
this._artifactReadyPromise = new _utils.ManualPromise();
|
||||||
|
this._isRemote = false;
|
||||||
|
this._isRemote = connection.isRemote();
|
||||||
|
this._artifact = page._closedOrCrashedScope.safeRace(this._artifactReadyPromise);
|
||||||
|
}
|
||||||
|
_artifactReady(artifact) {
|
||||||
|
this._artifactReadyPromise.resolve(artifact);
|
||||||
|
}
|
||||||
|
async path() {
|
||||||
|
if (this._isRemote) throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
|
||||||
|
const artifact = await this._artifact;
|
||||||
|
if (!artifact) throw new Error('Page did not produce any video frames');
|
||||||
|
return artifact._initializer.absolutePath;
|
||||||
|
}
|
||||||
|
async saveAs(path) {
|
||||||
|
const artifact = await this._artifact;
|
||||||
|
if (!artifact) throw new Error('Page did not produce any video frames');
|
||||||
|
return artifact.saveAs(path);
|
||||||
|
}
|
||||||
|
async delete() {
|
||||||
|
const artifact = await this._artifact;
|
||||||
|
if (artifact) await artifact.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Video = Video;
|
@ -0,0 +1,158 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Waiter = void 0;
|
||||||
|
var _stackTrace = require("../utils/stackTrace");
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Waiter {
|
||||||
|
constructor(channelOwner, event) {
|
||||||
|
this._dispose = void 0;
|
||||||
|
this._failures = [];
|
||||||
|
this._immediateError = void 0;
|
||||||
|
this._logs = [];
|
||||||
|
this._channelOwner = void 0;
|
||||||
|
this._waitId = void 0;
|
||||||
|
this._error = void 0;
|
||||||
|
this._waitId = (0, _utils.createGuid)();
|
||||||
|
this._channelOwner = channelOwner;
|
||||||
|
this._channelOwner._channel.waitForEventInfo({
|
||||||
|
info: {
|
||||||
|
waitId: this._waitId,
|
||||||
|
phase: 'before',
|
||||||
|
event
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
this._dispose = [() => this._channelOwner._wrapApiCall(async () => {
|
||||||
|
await this._channelOwner._channel.waitForEventInfo({
|
||||||
|
info: {
|
||||||
|
waitId: this._waitId,
|
||||||
|
phase: 'after',
|
||||||
|
error: this._error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, true).catch(() => {})];
|
||||||
|
}
|
||||||
|
static createForEvent(channelOwner, event) {
|
||||||
|
return new Waiter(channelOwner, event);
|
||||||
|
}
|
||||||
|
async waitForEvent(emitter, event, predicate) {
|
||||||
|
const {
|
||||||
|
promise,
|
||||||
|
dispose
|
||||||
|
} = waitForEvent(emitter, event, predicate);
|
||||||
|
return this.waitForPromise(promise, dispose);
|
||||||
|
}
|
||||||
|
rejectOnEvent(emitter, event, error, predicate) {
|
||||||
|
const {
|
||||||
|
promise,
|
||||||
|
dispose
|
||||||
|
} = waitForEvent(emitter, event, predicate);
|
||||||
|
this._rejectOn(promise.then(() => {
|
||||||
|
throw error;
|
||||||
|
}), dispose);
|
||||||
|
}
|
||||||
|
rejectOnTimeout(timeout, message) {
|
||||||
|
if (!timeout) return;
|
||||||
|
const {
|
||||||
|
promise,
|
||||||
|
dispose
|
||||||
|
} = waitForTimeout(timeout);
|
||||||
|
this._rejectOn(promise.then(() => {
|
||||||
|
throw new _errors.TimeoutError(message);
|
||||||
|
}), dispose);
|
||||||
|
}
|
||||||
|
rejectImmediately(error) {
|
||||||
|
this._immediateError = error;
|
||||||
|
}
|
||||||
|
dispose() {
|
||||||
|
for (const dispose of this._dispose) dispose();
|
||||||
|
}
|
||||||
|
async waitForPromise(promise, dispose) {
|
||||||
|
try {
|
||||||
|
if (this._immediateError) throw this._immediateError;
|
||||||
|
const result = await Promise.race([promise, ...this._failures]);
|
||||||
|
if (dispose) dispose();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
if (dispose) dispose();
|
||||||
|
this._error = e.message;
|
||||||
|
this.dispose();
|
||||||
|
(0, _stackTrace.rewriteErrorMessage)(e, e.message + formatLogRecording(this._logs));
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log(s) {
|
||||||
|
this._logs.push(s);
|
||||||
|
this._channelOwner._wrapApiCall(async () => {
|
||||||
|
await this._channelOwner._channel.waitForEventInfo({
|
||||||
|
info: {
|
||||||
|
waitId: this._waitId,
|
||||||
|
phase: 'log',
|
||||||
|
message: s
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
_rejectOn(promise, dispose) {
|
||||||
|
this._failures.push(promise);
|
||||||
|
if (dispose) this._dispose.push(dispose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Waiter = Waiter;
|
||||||
|
function waitForEvent(emitter, event, predicate) {
|
||||||
|
let listener;
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
listener = async eventArg => {
|
||||||
|
try {
|
||||||
|
if (predicate && !(await predicate(eventArg))) return;
|
||||||
|
emitter.removeListener(event, listener);
|
||||||
|
resolve(eventArg);
|
||||||
|
} catch (e) {
|
||||||
|
emitter.removeListener(event, listener);
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
emitter.addListener(event, listener);
|
||||||
|
});
|
||||||
|
const dispose = () => emitter.removeListener(event, listener);
|
||||||
|
return {
|
||||||
|
promise,
|
||||||
|
dispose
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function waitForTimeout(timeout) {
|
||||||
|
let timeoutId;
|
||||||
|
const promise = new Promise(resolve => timeoutId = setTimeout(resolve, timeout));
|
||||||
|
const dispose = () => clearTimeout(timeoutId);
|
||||||
|
return {
|
||||||
|
promise,
|
||||||
|
dispose
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function formatLogRecording(log) {
|
||||||
|
if (!log.length) return '';
|
||||||
|
const header = ` logs `;
|
||||||
|
const headerLength = 60;
|
||||||
|
const leftLength = (headerLength - header.length) / 2;
|
||||||
|
const rightLength = headerLength - header.length - leftLength;
|
||||||
|
return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Worker = void 0;
|
||||||
|
var _events = require("./events");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
var _jsHandle = require("./jsHandle");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Worker extends _channelOwner.ChannelOwner {
|
||||||
|
// Set for web workers.
|
||||||
|
// Set for service workers.
|
||||||
|
|
||||||
|
static from(worker) {
|
||||||
|
return worker._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
this._page = void 0;
|
||||||
|
this._context = void 0;
|
||||||
|
this._closedScope = new _utils.LongStandingScope();
|
||||||
|
this._channel.on('close', () => {
|
||||||
|
if (this._page) this._page._workers.delete(this);
|
||||||
|
if (this._context) this._context._serviceWorkers.delete(this);
|
||||||
|
this.emit(_events.Events.Worker.Close, this);
|
||||||
|
});
|
||||||
|
this.once(_events.Events.Worker.Close, () => this._closedScope.close(_errors.kBrowserOrContextClosedError));
|
||||||
|
}
|
||||||
|
url() {
|
||||||
|
return this._initializer.url;
|
||||||
|
}
|
||||||
|
async evaluate(pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
|
||||||
|
const result = await this._channel.evaluateExpression({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return (0, _jsHandle.parseResult)(result.value);
|
||||||
|
}
|
||||||
|
async evaluateHandle(pageFunction, arg) {
|
||||||
|
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
|
||||||
|
const result = await this._channel.evaluateExpressionHandle({
|
||||||
|
expression: String(pageFunction),
|
||||||
|
isFunction: typeof pageFunction === 'function',
|
||||||
|
arg: (0, _jsHandle.serializeArgument)(arg)
|
||||||
|
});
|
||||||
|
return _jsHandle.JSHandle.from(result.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Worker = Worker;
|
@ -0,0 +1,54 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.WritableStream = void 0;
|
||||||
|
var _stream = require("stream");
|
||||||
|
var _channelOwner = require("./channelOwner");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class WritableStream extends _channelOwner.ChannelOwner {
|
||||||
|
static from(Stream) {
|
||||||
|
return Stream._object;
|
||||||
|
}
|
||||||
|
constructor(parent, type, guid, initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
}
|
||||||
|
stream() {
|
||||||
|
return new WritableStreamImpl(this._channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.WritableStream = WritableStream;
|
||||||
|
class WritableStreamImpl extends _stream.Writable {
|
||||||
|
constructor(channel) {
|
||||||
|
super();
|
||||||
|
this._channel = void 0;
|
||||||
|
this._channel = channel;
|
||||||
|
}
|
||||||
|
async _write(chunk, encoding, callback) {
|
||||||
|
const error = await this._channel.write({
|
||||||
|
binary: typeof chunk === 'string' ? Buffer.from(chunk) : chunk
|
||||||
|
}).catch(e => e);
|
||||||
|
callback(error || null);
|
||||||
|
}
|
||||||
|
async _final(callback) {
|
||||||
|
// Stream might be destroyed after the connection was closed.
|
||||||
|
const error = await this._channel.close().catch(e => e);
|
||||||
|
callback(error || null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.debugLogger = exports.RecentLogsCollector = void 0;
|
||||||
|
var _utilsBundle = require("../utilsBundle");
|
||||||
|
var _fs = _interopRequireDefault(require("fs"));
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* 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 debugLoggerColorMap = {
|
||||||
|
'api': 45,
|
||||||
|
// cyan
|
||||||
|
'protocol': 34,
|
||||||
|
// green
|
||||||
|
'install': 34,
|
||||||
|
// green
|
||||||
|
'download': 34,
|
||||||
|
// green
|
||||||
|
'browser': 0,
|
||||||
|
// reset
|
||||||
|
'socks': 92,
|
||||||
|
// purple
|
||||||
|
'error': 160,
|
||||||
|
// red,
|
||||||
|
'channel:command': 33,
|
||||||
|
// blue
|
||||||
|
'channel:response': 202,
|
||||||
|
// orange
|
||||||
|
'channel:event': 207,
|
||||||
|
// magenta
|
||||||
|
'server': 45,
|
||||||
|
// cyan
|
||||||
|
'server:channel': 34 // green
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugLogger {
|
||||||
|
constructor() {
|
||||||
|
this._debuggers = new Map();
|
||||||
|
if (process.env.DEBUG_FILE) {
|
||||||
|
const ansiRegex = new RegExp(['[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'].join('|'), 'g');
|
||||||
|
const stream = _fs.default.createWriteStream(process.env.DEBUG_FILE);
|
||||||
|
_utilsBundle.debug.log = data => {
|
||||||
|
stream.write(data.replace(ansiRegex, ''));
|
||||||
|
stream.write('\n');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log(name, message) {
|
||||||
|
let cachedDebugger = this._debuggers.get(name);
|
||||||
|
if (!cachedDebugger) {
|
||||||
|
cachedDebugger = (0, _utilsBundle.debug)(`pw:${name}`);
|
||||||
|
this._debuggers.set(name, cachedDebugger);
|
||||||
|
cachedDebugger.color = debugLoggerColorMap[name];
|
||||||
|
}
|
||||||
|
cachedDebugger(message);
|
||||||
|
}
|
||||||
|
isEnabled(name) {
|
||||||
|
return _utilsBundle.debug.enabled(`pw:${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const debugLogger = new DebugLogger();
|
||||||
|
exports.debugLogger = debugLogger;
|
||||||
|
const kLogCount = 150;
|
||||||
|
class RecentLogsCollector {
|
||||||
|
constructor() {
|
||||||
|
this._logs = [];
|
||||||
|
}
|
||||||
|
log(message) {
|
||||||
|
this._logs.push(message);
|
||||||
|
if (this._logs.length === kLogCount * 2) this._logs.splice(0, kLogCount);
|
||||||
|
}
|
||||||
|
recentLogs() {
|
||||||
|
if (this._logs.length > kLogCount) return this._logs.slice(-kLogCount);
|
||||||
|
return this._logs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.RecentLogsCollector = RecentLogsCollector;
|
@ -0,0 +1,41 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.TimeoutError = void 0;
|
||||||
|
exports.isSafeCloseError = isSafeCloseError;
|
||||||
|
exports.kBrowserOrContextClosedError = exports.kBrowserClosedError = void 0;
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CustomError extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class TimeoutError extends CustomError {}
|
||||||
|
exports.TimeoutError = TimeoutError;
|
||||||
|
const kBrowserClosedError = 'Browser has been closed';
|
||||||
|
exports.kBrowserClosedError = kBrowserClosedError;
|
||||||
|
const kBrowserOrContextClosedError = 'Target page, context or browser has been closed';
|
||||||
|
exports.kBrowserOrContextClosedError = kBrowserOrContextClosedError;
|
||||||
|
function isSafeCloseError(error) {
|
||||||
|
return error.message.endsWith(kBrowserClosedError) || error.message.endsWith(kBrowserOrContextClosedError);
|
||||||
|
}
|
@ -0,0 +1,574 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.SocksProxyHandler = exports.SocksProxy = void 0;
|
||||||
|
exports.parsePattern = parsePattern;
|
||||||
|
var _events = _interopRequireDefault(require("events"));
|
||||||
|
var _net = _interopRequireDefault(require("net"));
|
||||||
|
var _debugLogger = require("./debugLogger");
|
||||||
|
var _happyEyeballs = require("../utils/happy-eyeballs");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// https://tools.ietf.org/html/rfc1928
|
||||||
|
var SocksAuth;
|
||||||
|
(function (SocksAuth) {
|
||||||
|
SocksAuth[SocksAuth["NO_AUTHENTICATION_REQUIRED"] = 0] = "NO_AUTHENTICATION_REQUIRED";
|
||||||
|
SocksAuth[SocksAuth["GSSAPI"] = 1] = "GSSAPI";
|
||||||
|
SocksAuth[SocksAuth["USERNAME_PASSWORD"] = 2] = "USERNAME_PASSWORD";
|
||||||
|
SocksAuth[SocksAuth["NO_ACCEPTABLE_METHODS"] = 255] = "NO_ACCEPTABLE_METHODS";
|
||||||
|
})(SocksAuth || (SocksAuth = {}));
|
||||||
|
var SocksAddressType;
|
||||||
|
(function (SocksAddressType) {
|
||||||
|
SocksAddressType[SocksAddressType["IPv4"] = 1] = "IPv4";
|
||||||
|
SocksAddressType[SocksAddressType["FqName"] = 3] = "FqName";
|
||||||
|
SocksAddressType[SocksAddressType["IPv6"] = 4] = "IPv6";
|
||||||
|
})(SocksAddressType || (SocksAddressType = {}));
|
||||||
|
var SocksCommand;
|
||||||
|
(function (SocksCommand) {
|
||||||
|
SocksCommand[SocksCommand["CONNECT"] = 1] = "CONNECT";
|
||||||
|
SocksCommand[SocksCommand["BIND"] = 2] = "BIND";
|
||||||
|
SocksCommand[SocksCommand["UDP_ASSOCIATE"] = 3] = "UDP_ASSOCIATE";
|
||||||
|
})(SocksCommand || (SocksCommand = {}));
|
||||||
|
var SocksReply;
|
||||||
|
(function (SocksReply) {
|
||||||
|
SocksReply[SocksReply["Succeeded"] = 0] = "Succeeded";
|
||||||
|
SocksReply[SocksReply["GeneralServerFailure"] = 1] = "GeneralServerFailure";
|
||||||
|
SocksReply[SocksReply["NotAllowedByRuleSet"] = 2] = "NotAllowedByRuleSet";
|
||||||
|
SocksReply[SocksReply["NetworkUnreachable"] = 3] = "NetworkUnreachable";
|
||||||
|
SocksReply[SocksReply["HostUnreachable"] = 4] = "HostUnreachable";
|
||||||
|
SocksReply[SocksReply["ConnectionRefused"] = 5] = "ConnectionRefused";
|
||||||
|
SocksReply[SocksReply["TtlExpired"] = 6] = "TtlExpired";
|
||||||
|
SocksReply[SocksReply["CommandNotSupported"] = 7] = "CommandNotSupported";
|
||||||
|
SocksReply[SocksReply["AddressTypeNotSupported"] = 8] = "AddressTypeNotSupported";
|
||||||
|
})(SocksReply || (SocksReply = {}));
|
||||||
|
class SocksConnection {
|
||||||
|
constructor(uid, socket, client) {
|
||||||
|
this._buffer = Buffer.from([]);
|
||||||
|
this._offset = 0;
|
||||||
|
this._fence = 0;
|
||||||
|
this._fenceCallback = void 0;
|
||||||
|
this._socket = void 0;
|
||||||
|
this._boundOnData = void 0;
|
||||||
|
this._uid = void 0;
|
||||||
|
this._client = void 0;
|
||||||
|
this._uid = uid;
|
||||||
|
this._socket = socket;
|
||||||
|
this._client = client;
|
||||||
|
this._boundOnData = this._onData.bind(this);
|
||||||
|
socket.on('data', this._boundOnData);
|
||||||
|
socket.on('close', () => this._onClose());
|
||||||
|
socket.on('end', () => this._onClose());
|
||||||
|
socket.on('error', () => this._onClose());
|
||||||
|
this._run().catch(() => this._socket.end());
|
||||||
|
}
|
||||||
|
async _run() {
|
||||||
|
(0, _utils.assert)(await this._authenticate());
|
||||||
|
const {
|
||||||
|
command,
|
||||||
|
host,
|
||||||
|
port
|
||||||
|
} = await this._parseRequest();
|
||||||
|
if (command !== SocksCommand.CONNECT) {
|
||||||
|
this._writeBytes(Buffer.from([0x05, SocksReply.CommandNotSupported, 0x00,
|
||||||
|
// RSV
|
||||||
|
0x01,
|
||||||
|
// IPv4
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
// Address
|
||||||
|
0x00, 0x00 // Port
|
||||||
|
]));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._socket.off('data', this._boundOnData);
|
||||||
|
this._client.onSocketRequested({
|
||||||
|
uid: this._uid,
|
||||||
|
host,
|
||||||
|
port
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _authenticate() {
|
||||||
|
// Request:
|
||||||
|
// +----+----------+----------+
|
||||||
|
// |VER | NMETHODS | METHODS |
|
||||||
|
// +----+----------+----------+
|
||||||
|
// | 1 | 1 | 1 to 255 |
|
||||||
|
// +----+----------+----------+
|
||||||
|
|
||||||
|
// Response:
|
||||||
|
// +----+--------+
|
||||||
|
// |VER | METHOD |
|
||||||
|
// +----+--------+
|
||||||
|
// | 1 | 1 |
|
||||||
|
// +----+--------+
|
||||||
|
|
||||||
|
const version = await this._readByte();
|
||||||
|
(0, _utils.assert)(version === 0x05, 'The VER field must be set to x05 for this version of the protocol, was ' + version);
|
||||||
|
const nMethods = await this._readByte();
|
||||||
|
(0, _utils.assert)(nMethods, 'No authentication methods specified');
|
||||||
|
const methods = await this._readBytes(nMethods);
|
||||||
|
for (const method of methods) {
|
||||||
|
if (method === 0) {
|
||||||
|
this._writeBytes(Buffer.from([version, method]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._writeBytes(Buffer.from([version, SocksAuth.NO_ACCEPTABLE_METHODS]));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
async _parseRequest() {
|
||||||
|
// Request.
|
||||||
|
// +----+-----+-------+------+----------+----------+
|
||||||
|
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
||||||
|
// +----+-----+-------+------+----------+----------+
|
||||||
|
// | 1 | 1 | X'00' | 1 | Variable | 2 |
|
||||||
|
// +----+-----+-------+------+----------+----------+
|
||||||
|
|
||||||
|
// Response.
|
||||||
|
// +----+-----+-------+------+----------+----------+
|
||||||
|
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
|
||||||
|
// +----+-----+-------+------+----------+----------+
|
||||||
|
// | 1 | 1 | X'00' | 1 | Variable | 2 |
|
||||||
|
// +----+-----+-------+------+----------+----------+
|
||||||
|
|
||||||
|
const version = await this._readByte();
|
||||||
|
(0, _utils.assert)(version === 0x05, 'The VER field must be set to x05 for this version of the protocol, was ' + version);
|
||||||
|
const command = await this._readByte();
|
||||||
|
await this._readByte(); // skip reserved.
|
||||||
|
const addressType = await this._readByte();
|
||||||
|
let host = '';
|
||||||
|
switch (addressType) {
|
||||||
|
case SocksAddressType.IPv4:
|
||||||
|
host = (await this._readBytes(4)).join('.');
|
||||||
|
break;
|
||||||
|
case SocksAddressType.FqName:
|
||||||
|
const length = await this._readByte();
|
||||||
|
host = (await this._readBytes(length)).toString();
|
||||||
|
break;
|
||||||
|
case SocksAddressType.IPv6:
|
||||||
|
const bytes = await this._readBytes(16);
|
||||||
|
const tokens = [];
|
||||||
|
for (let i = 0; i < 8; ++i) tokens.push(bytes.readUInt16BE(i * 2).toString(16));
|
||||||
|
host = tokens.join(':');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const port = (await this._readBytes(2)).readUInt16BE(0);
|
||||||
|
this._buffer = Buffer.from([]);
|
||||||
|
this._offset = 0;
|
||||||
|
this._fence = 0;
|
||||||
|
return {
|
||||||
|
command,
|
||||||
|
host,
|
||||||
|
port
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async _readByte() {
|
||||||
|
const buffer = await this._readBytes(1);
|
||||||
|
return buffer[0];
|
||||||
|
}
|
||||||
|
async _readBytes(length) {
|
||||||
|
this._fence = this._offset + length;
|
||||||
|
if (!this._buffer || this._buffer.length < this._fence) await new Promise(f => this._fenceCallback = f);
|
||||||
|
this._offset += length;
|
||||||
|
return this._buffer.slice(this._offset - length, this._offset);
|
||||||
|
}
|
||||||
|
_writeBytes(buffer) {
|
||||||
|
if (this._socket.writable) this._socket.write(buffer);
|
||||||
|
}
|
||||||
|
_onClose() {
|
||||||
|
this._client.onSocketClosed({
|
||||||
|
uid: this._uid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_onData(buffer) {
|
||||||
|
this._buffer = Buffer.concat([this._buffer, buffer]);
|
||||||
|
if (this._fenceCallback && this._buffer.length >= this._fence) {
|
||||||
|
const callback = this._fenceCallback;
|
||||||
|
this._fenceCallback = undefined;
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socketConnected(host, port) {
|
||||||
|
this._writeBytes(Buffer.from([0x05, SocksReply.Succeeded, 0x00,
|
||||||
|
// RSV
|
||||||
|
...ipToSocksAddress(host),
|
||||||
|
// ATYP, Address
|
||||||
|
port >> 8, port & 0xFF // Port
|
||||||
|
]));
|
||||||
|
|
||||||
|
this._socket.on('data', data => this._client.onSocketData({
|
||||||
|
uid: this._uid,
|
||||||
|
data
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
socketFailed(errorCode) {
|
||||||
|
const buffer = Buffer.from([0x05, 0, 0x00,
|
||||||
|
// RSV
|
||||||
|
...ipToSocksAddress('0.0.0.0'),
|
||||||
|
// ATYP, Address
|
||||||
|
0, 0 // Port
|
||||||
|
]);
|
||||||
|
|
||||||
|
switch (errorCode) {
|
||||||
|
case 'ENOENT':
|
||||||
|
case 'ENOTFOUND':
|
||||||
|
case 'ETIMEDOUT':
|
||||||
|
case 'EHOSTUNREACH':
|
||||||
|
buffer[1] = SocksReply.HostUnreachable;
|
||||||
|
break;
|
||||||
|
case 'ENETUNREACH':
|
||||||
|
buffer[1] = SocksReply.NetworkUnreachable;
|
||||||
|
break;
|
||||||
|
case 'ECONNREFUSED':
|
||||||
|
buffer[1] = SocksReply.ConnectionRefused;
|
||||||
|
break;
|
||||||
|
case 'ERULESET':
|
||||||
|
buffer[1] = SocksReply.NotAllowedByRuleSet;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this._writeBytes(buffer);
|
||||||
|
this._socket.end();
|
||||||
|
}
|
||||||
|
sendData(data) {
|
||||||
|
this._socket.write(data);
|
||||||
|
}
|
||||||
|
end() {
|
||||||
|
this._socket.end();
|
||||||
|
}
|
||||||
|
error(error) {
|
||||||
|
this._socket.destroy(new Error(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hexToNumber(hex) {
|
||||||
|
// Note: parseInt has a few issues including ignoring trailing characters and allowing leading 0x.
|
||||||
|
return [...hex].reduce((value, digit) => {
|
||||||
|
const code = digit.charCodeAt(0);
|
||||||
|
if (code >= 48 && code <= 57)
|
||||||
|
// 0..9
|
||||||
|
return value + code;
|
||||||
|
if (code >= 97 && code <= 102)
|
||||||
|
// a..f
|
||||||
|
return value + (code - 97) + 10;
|
||||||
|
if (code >= 65 && code <= 70)
|
||||||
|
// A..F
|
||||||
|
return value + (code - 65) + 10;
|
||||||
|
throw new Error('Invalid IPv6 token ' + hex);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
function ipToSocksAddress(address) {
|
||||||
|
if (_net.default.isIPv4(address)) {
|
||||||
|
return [0x01,
|
||||||
|
// IPv4
|
||||||
|
...address.split('.', 4).map(t => +t & 0xFF) // Address
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_net.default.isIPv6(address)) {
|
||||||
|
const result = [0x04]; // IPv6
|
||||||
|
const tokens = address.split(':', 8);
|
||||||
|
while (tokens.length < 8) tokens.unshift('');
|
||||||
|
for (const token of tokens) {
|
||||||
|
const value = hexToNumber(token);
|
||||||
|
result.push(value >> 8 & 0xFF, value & 0xFF); // Big-endian
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw new Error('Only IPv4 and IPv6 addresses are supported');
|
||||||
|
}
|
||||||
|
function starMatchToRegex(pattern) {
|
||||||
|
const source = pattern.split('*').map(s => {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
||||||
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}).join('.*');
|
||||||
|
return new RegExp('^' + source + '$');
|
||||||
|
}
|
||||||
|
|
||||||
|
// This follows "Proxy bypass rules" syntax without implicit and negative rules.
|
||||||
|
// https://source.chromium.org/chromium/chromium/src/+/main:net/docs/proxy.md;l=331
|
||||||
|
function parsePattern(pattern) {
|
||||||
|
if (!pattern) return () => false;
|
||||||
|
const matchers = pattern.split(',').map(token => {
|
||||||
|
const match = token.match(/^(.*?)(?::(\d+))?$/);
|
||||||
|
if (!match) throw new Error(`Unsupported token "${token}" in pattern "${pattern}"`);
|
||||||
|
const tokenPort = match[2] ? +match[2] : undefined;
|
||||||
|
const portMatches = port => tokenPort === undefined || tokenPort === port;
|
||||||
|
let tokenHost = match[1];
|
||||||
|
if (tokenHost === '<loopback>') {
|
||||||
|
return (host, port) => {
|
||||||
|
if (!portMatches(port)) return false;
|
||||||
|
return host === 'localhost' || host.endsWith('.localhost') || host === '127.0.0.1' || host === '[::1]';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (tokenHost === '*') return (host, port) => portMatches(port);
|
||||||
|
if (_net.default.isIPv4(tokenHost) || _net.default.isIPv6(tokenHost)) return (host, port) => host === tokenHost && portMatches(port);
|
||||||
|
if (tokenHost[0] === '.') tokenHost = '*' + tokenHost;
|
||||||
|
const tokenRegex = starMatchToRegex(tokenHost);
|
||||||
|
return (host, port) => {
|
||||||
|
if (!portMatches(port)) return false;
|
||||||
|
if (_net.default.isIPv4(host) || _net.default.isIPv6(host)) return false;
|
||||||
|
return !!host.match(tokenRegex);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return (host, port) => matchers.some(matcher => matcher(host, port));
|
||||||
|
}
|
||||||
|
class SocksProxy extends _events.default {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._server = void 0;
|
||||||
|
this._connections = new Map();
|
||||||
|
this._sockets = new Set();
|
||||||
|
this._closed = false;
|
||||||
|
this._port = void 0;
|
||||||
|
this._patternMatcher = () => false;
|
||||||
|
this._directSockets = new Map();
|
||||||
|
this._server = new _net.default.Server(socket => {
|
||||||
|
const uid = (0, _utils.createGuid)();
|
||||||
|
const connection = new SocksConnection(uid, socket, this);
|
||||||
|
this._connections.set(uid, connection);
|
||||||
|
});
|
||||||
|
this._server.on('connection', socket => {
|
||||||
|
if (this._closed) {
|
||||||
|
socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._sockets.add(socket);
|
||||||
|
socket.once('close', () => this._sockets.delete(socket));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setPattern(pattern) {
|
||||||
|
try {
|
||||||
|
this._patternMatcher = parsePattern(pattern);
|
||||||
|
} catch (e) {
|
||||||
|
this._patternMatcher = () => false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async _handleDirect(request) {
|
||||||
|
try {
|
||||||
|
var _this$_connections$ge4;
|
||||||
|
const socket = await (0, _happyEyeballs.createSocket)(request.host, request.port);
|
||||||
|
socket.on('data', data => {
|
||||||
|
var _this$_connections$ge;
|
||||||
|
return (_this$_connections$ge = this._connections.get(request.uid)) === null || _this$_connections$ge === void 0 ? void 0 : _this$_connections$ge.sendData(data);
|
||||||
|
});
|
||||||
|
socket.on('error', error => {
|
||||||
|
var _this$_connections$ge2;
|
||||||
|
(_this$_connections$ge2 = this._connections.get(request.uid)) === null || _this$_connections$ge2 === void 0 ? void 0 : _this$_connections$ge2.error(error.message);
|
||||||
|
this._directSockets.delete(request.uid);
|
||||||
|
});
|
||||||
|
socket.on('end', () => {
|
||||||
|
var _this$_connections$ge3;
|
||||||
|
(_this$_connections$ge3 = this._connections.get(request.uid)) === null || _this$_connections$ge3 === void 0 ? void 0 : _this$_connections$ge3.end();
|
||||||
|
this._directSockets.delete(request.uid);
|
||||||
|
});
|
||||||
|
const localAddress = socket.localAddress;
|
||||||
|
const localPort = socket.localPort;
|
||||||
|
this._directSockets.set(request.uid, socket);
|
||||||
|
(_this$_connections$ge4 = this._connections.get(request.uid)) === null || _this$_connections$ge4 === void 0 ? void 0 : _this$_connections$ge4.socketConnected(localAddress, localPort);
|
||||||
|
} catch (error) {
|
||||||
|
var _this$_connections$ge5;
|
||||||
|
(_this$_connections$ge5 = this._connections.get(request.uid)) === null || _this$_connections$ge5 === void 0 ? void 0 : _this$_connections$ge5.socketFailed(error.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port() {
|
||||||
|
return this._port;
|
||||||
|
}
|
||||||
|
async listen(port) {
|
||||||
|
return new Promise(f => {
|
||||||
|
this._server.listen(port, () => {
|
||||||
|
const port = this._server.address().port;
|
||||||
|
this._port = port;
|
||||||
|
f(port);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
if (this._closed) return;
|
||||||
|
this._closed = true;
|
||||||
|
for (const socket of this._sockets) socket.destroy();
|
||||||
|
this._sockets.clear();
|
||||||
|
await new Promise(f => this._server.close(f));
|
||||||
|
}
|
||||||
|
onSocketRequested(payload) {
|
||||||
|
if (!this._patternMatcher(payload.host, payload.port)) {
|
||||||
|
this._handleDirect(payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit(SocksProxy.Events.SocksRequested, payload);
|
||||||
|
}
|
||||||
|
onSocketData(payload) {
|
||||||
|
const direct = this._directSockets.get(payload.uid);
|
||||||
|
if (direct) {
|
||||||
|
direct.write(payload.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit(SocksProxy.Events.SocksData, payload);
|
||||||
|
}
|
||||||
|
onSocketClosed(payload) {
|
||||||
|
const direct = this._directSockets.get(payload.uid);
|
||||||
|
if (direct) {
|
||||||
|
direct.destroy();
|
||||||
|
this._directSockets.delete(payload.uid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit(SocksProxy.Events.SocksClosed, payload);
|
||||||
|
}
|
||||||
|
socketConnected({
|
||||||
|
uid,
|
||||||
|
host,
|
||||||
|
port
|
||||||
|
}) {
|
||||||
|
var _this$_connections$ge6;
|
||||||
|
(_this$_connections$ge6 = this._connections.get(uid)) === null || _this$_connections$ge6 === void 0 ? void 0 : _this$_connections$ge6.socketConnected(host, port);
|
||||||
|
}
|
||||||
|
socketFailed({
|
||||||
|
uid,
|
||||||
|
errorCode
|
||||||
|
}) {
|
||||||
|
var _this$_connections$ge7;
|
||||||
|
(_this$_connections$ge7 = this._connections.get(uid)) === null || _this$_connections$ge7 === void 0 ? void 0 : _this$_connections$ge7.socketFailed(errorCode);
|
||||||
|
}
|
||||||
|
sendSocketData({
|
||||||
|
uid,
|
||||||
|
data
|
||||||
|
}) {
|
||||||
|
var _this$_connections$ge8;
|
||||||
|
(_this$_connections$ge8 = this._connections.get(uid)) === null || _this$_connections$ge8 === void 0 ? void 0 : _this$_connections$ge8.sendData(data);
|
||||||
|
}
|
||||||
|
sendSocketEnd({
|
||||||
|
uid
|
||||||
|
}) {
|
||||||
|
var _this$_connections$ge9;
|
||||||
|
(_this$_connections$ge9 = this._connections.get(uid)) === null || _this$_connections$ge9 === void 0 ? void 0 : _this$_connections$ge9.end();
|
||||||
|
}
|
||||||
|
sendSocketError({
|
||||||
|
uid,
|
||||||
|
error
|
||||||
|
}) {
|
||||||
|
var _this$_connections$ge10;
|
||||||
|
(_this$_connections$ge10 = this._connections.get(uid)) === null || _this$_connections$ge10 === void 0 ? void 0 : _this$_connections$ge10.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.SocksProxy = SocksProxy;
|
||||||
|
SocksProxy.Events = {
|
||||||
|
SocksRequested: 'socksRequested',
|
||||||
|
SocksData: 'socksData',
|
||||||
|
SocksClosed: 'socksClosed'
|
||||||
|
};
|
||||||
|
class SocksProxyHandler extends _events.default {
|
||||||
|
constructor(pattern, redirectPortForTest) {
|
||||||
|
super();
|
||||||
|
this._sockets = new Map();
|
||||||
|
this._patternMatcher = () => false;
|
||||||
|
this._redirectPortForTest = void 0;
|
||||||
|
this._patternMatcher = parsePattern(pattern);
|
||||||
|
this._redirectPortForTest = redirectPortForTest;
|
||||||
|
}
|
||||||
|
cleanup() {
|
||||||
|
for (const uid of this._sockets.keys()) this.socketClosed({
|
||||||
|
uid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async socketRequested({
|
||||||
|
uid,
|
||||||
|
host,
|
||||||
|
port
|
||||||
|
}) {
|
||||||
|
_debugLogger.debugLogger.log('socks', `[${uid}] => request ${host}:${port}`);
|
||||||
|
if (!this._patternMatcher(host, port)) {
|
||||||
|
const payload = {
|
||||||
|
uid,
|
||||||
|
errorCode: 'ERULESET'
|
||||||
|
};
|
||||||
|
_debugLogger.debugLogger.log('socks', `[${uid}] <= pattern error ${payload.errorCode}`);
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksFailed, payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (host === 'local.playwright') host = 'localhost';
|
||||||
|
try {
|
||||||
|
if (this._redirectPortForTest) port = this._redirectPortForTest;
|
||||||
|
const socket = await (0, _happyEyeballs.createSocket)(host, port);
|
||||||
|
socket.on('data', data => {
|
||||||
|
const payload = {
|
||||||
|
uid,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksData, payload);
|
||||||
|
});
|
||||||
|
socket.on('error', error => {
|
||||||
|
const payload = {
|
||||||
|
uid,
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
_debugLogger.debugLogger.log('socks', `[${uid}] <= network socket error ${payload.error}`);
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksError, payload);
|
||||||
|
this._sockets.delete(uid);
|
||||||
|
});
|
||||||
|
socket.on('end', () => {
|
||||||
|
const payload = {
|
||||||
|
uid
|
||||||
|
};
|
||||||
|
_debugLogger.debugLogger.log('socks', `[${uid}] <= network socket closed`);
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksEnd, payload);
|
||||||
|
this._sockets.delete(uid);
|
||||||
|
});
|
||||||
|
const localAddress = socket.localAddress;
|
||||||
|
const localPort = socket.localPort;
|
||||||
|
this._sockets.set(uid, socket);
|
||||||
|
const payload = {
|
||||||
|
uid,
|
||||||
|
host: localAddress,
|
||||||
|
port: localPort
|
||||||
|
};
|
||||||
|
_debugLogger.debugLogger.log('socks', `[${uid}] <= connected to network ${payload.host}:${payload.port}`);
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksConnected, payload);
|
||||||
|
} catch (error) {
|
||||||
|
const payload = {
|
||||||
|
uid,
|
||||||
|
errorCode: error.code
|
||||||
|
};
|
||||||
|
_debugLogger.debugLogger.log('socks', `[${uid}] <= connect error ${payload.errorCode}`);
|
||||||
|
this.emit(SocksProxyHandler.Events.SocksFailed, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendSocketData({
|
||||||
|
uid,
|
||||||
|
data
|
||||||
|
}) {
|
||||||
|
var _this$_sockets$get;
|
||||||
|
(_this$_sockets$get = this._sockets.get(uid)) === null || _this$_sockets$get === void 0 ? void 0 : _this$_sockets$get.write(data);
|
||||||
|
}
|
||||||
|
socketClosed({
|
||||||
|
uid
|
||||||
|
}) {
|
||||||
|
var _this$_sockets$get2;
|
||||||
|
_debugLogger.debugLogger.log('socks', `[${uid}] <= browser socket closed`);
|
||||||
|
(_this$_sockets$get2 = this._sockets.get(uid)) === null || _this$_sockets$get2 === void 0 ? void 0 : _this$_sockets$get2.destroy();
|
||||||
|
this._sockets.delete(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.SocksProxyHandler = SocksProxyHandler;
|
||||||
|
SocksProxyHandler.Events = {
|
||||||
|
SocksConnected: 'socksConnected',
|
||||||
|
SocksData: 'socksData',
|
||||||
|
SocksError: 'socksError',
|
||||||
|
SocksFailed: 'socksFailed',
|
||||||
|
SocksEnd: 'socksEnd'
|
||||||
|
};
|
@ -0,0 +1,74 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.TimeoutSettings = exports.DEFAULT_TIMEOUT = exports.DEFAULT_LAUNCH_TIMEOUT = void 0;
|
||||||
|
var _utils = require("../utils");
|
||||||
|
/**
|
||||||
|
* Copyright 2019 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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 DEFAULT_TIMEOUT = 30000;
|
||||||
|
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
|
||||||
|
const DEFAULT_LAUNCH_TIMEOUT = 3 * 60 * 1000; // 3 minutes
|
||||||
|
exports.DEFAULT_LAUNCH_TIMEOUT = DEFAULT_LAUNCH_TIMEOUT;
|
||||||
|
class TimeoutSettings {
|
||||||
|
constructor(parent) {
|
||||||
|
this._parent = void 0;
|
||||||
|
this._defaultTimeout = void 0;
|
||||||
|
this._defaultNavigationTimeout = void 0;
|
||||||
|
this._parent = parent;
|
||||||
|
}
|
||||||
|
setDefaultTimeout(timeout) {
|
||||||
|
this._defaultTimeout = timeout;
|
||||||
|
}
|
||||||
|
setDefaultNavigationTimeout(timeout) {
|
||||||
|
this._defaultNavigationTimeout = timeout;
|
||||||
|
}
|
||||||
|
defaultNavigationTimeout() {
|
||||||
|
return this._defaultNavigationTimeout;
|
||||||
|
}
|
||||||
|
defaultTimeout() {
|
||||||
|
return this._defaultTimeout;
|
||||||
|
}
|
||||||
|
navigationTimeout(options) {
|
||||||
|
if (typeof options.timeout === 'number') return options.timeout;
|
||||||
|
if (this._defaultNavigationTimeout !== undefined) return this._defaultNavigationTimeout;
|
||||||
|
if ((0, _utils.debugMode)()) return 0;
|
||||||
|
if (this._defaultTimeout !== undefined) return this._defaultTimeout;
|
||||||
|
if (this._parent) return this._parent.navigationTimeout(options);
|
||||||
|
return DEFAULT_TIMEOUT;
|
||||||
|
}
|
||||||
|
timeout(options) {
|
||||||
|
if (typeof options.timeout === 'number') return options.timeout;
|
||||||
|
if ((0, _utils.debugMode)()) return 0;
|
||||||
|
if (this._defaultTimeout !== undefined) return this._defaultTimeout;
|
||||||
|
if (this._parent) return this._parent.timeout(options);
|
||||||
|
return DEFAULT_TIMEOUT;
|
||||||
|
}
|
||||||
|
static timeout(options) {
|
||||||
|
if (typeof options.timeout === 'number') return options.timeout;
|
||||||
|
if ((0, _utils.debugMode)()) return 0;
|
||||||
|
return DEFAULT_TIMEOUT;
|
||||||
|
}
|
||||||
|
static launchTimeout(options) {
|
||||||
|
if (typeof options.timeout === 'number') return options.timeout;
|
||||||
|
if ((0, _utils.debugMode)()) return 0;
|
||||||
|
return DEFAULT_LAUNCH_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.TimeoutSettings = TimeoutSettings;
|
@ -0,0 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,98 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.blendWithWhite = blendWithWhite;
|
||||||
|
exports.colorDeltaE94 = colorDeltaE94;
|
||||||
|
exports.rgb2gray = rgb2gray;
|
||||||
|
exports.srgb2xyz = srgb2xyz;
|
||||||
|
exports.xyz2lab = xyz2lab;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function blendWithWhite(c, a) {
|
||||||
|
return 255 + (c - 255) * a;
|
||||||
|
}
|
||||||
|
function rgb2gray(r, g, b) {
|
||||||
|
// NOTE: this is the exact integer formula from SSIM.js.
|
||||||
|
// See https://github.com/obartra/ssim/blob/ca8e3c6a6ff5f4f2e232239e0c3d91806f3c97d5/src/matlab/rgb2gray.ts#L56
|
||||||
|
return 77 * r + 150 * g + 29 * b + 128 >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Percieved color difference defined by CIE94.
|
||||||
|
// See https://en.wikipedia.org/wiki/Color_difference#CIE94
|
||||||
|
//
|
||||||
|
// The result of 1.0 is a "just-noticiable difference".
|
||||||
|
//
|
||||||
|
// Other results interpretation (taken from http://zschuessler.github.io/DeltaE/learn/):
|
||||||
|
// < 1.0 Not perceptible by human eyes.
|
||||||
|
// 1-2 Perceptible through close observation.
|
||||||
|
// 2-10 Perceptible at a glance.
|
||||||
|
// 11-49 Colors are more similar than opposite
|
||||||
|
// 100 Colors are exact opposite
|
||||||
|
function colorDeltaE94(rgb1, rgb2) {
|
||||||
|
const [l1, a1, b1] = xyz2lab(srgb2xyz(rgb1));
|
||||||
|
const [l2, a2, b2] = xyz2lab(srgb2xyz(rgb2));
|
||||||
|
const deltaL = l1 - l2;
|
||||||
|
const deltaA = a1 - a2;
|
||||||
|
const deltaB = b1 - b2;
|
||||||
|
const c1 = Math.sqrt(a1 ** 2 + b1 ** 2);
|
||||||
|
const c2 = Math.sqrt(a2 ** 2 + b2 ** 2);
|
||||||
|
const deltaC = c1 - c2;
|
||||||
|
let deltaH = deltaA ** 2 + deltaB ** 2 - deltaC ** 2;
|
||||||
|
deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
|
||||||
|
// The k1, k2, kL, kC, kH values for "graphic arts" applications.
|
||||||
|
// See https://en.wikipedia.org/wiki/Color_difference#CIE94
|
||||||
|
const k1 = 0.045;
|
||||||
|
const k2 = 0.015;
|
||||||
|
const kL = 1;
|
||||||
|
const kC = 1;
|
||||||
|
const kH = 1;
|
||||||
|
const sC = 1.0 + k1 * c1;
|
||||||
|
const sH = 1.0 + k2 * c1;
|
||||||
|
const sL = 1;
|
||||||
|
return Math.sqrt((deltaL / sL / kL) ** 2 + (deltaC / sC / kC) ** 2 + (deltaH / sH / kH) ** 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sRGB -> 1-normalized XYZ (i.e. Y ∈ [0, 1]) with D65 illuminant
|
||||||
|
// See https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
|
||||||
|
function srgb2xyz(rgb) {
|
||||||
|
let r = rgb[0] / 255;
|
||||||
|
let g = rgb[1] / 255;
|
||||||
|
let b = rgb[2] / 255;
|
||||||
|
r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
|
||||||
|
g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
|
||||||
|
b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
|
||||||
|
return [r * 0.4124 + g * 0.3576 + b * 0.1805, r * 0.2126 + g * 0.7152 + b * 0.0722, r * 0.0193 + g * 0.1192 + b * 0.9505];
|
||||||
|
}
|
||||||
|
const sigma_pow2 = 6 * 6 / 29 / 29;
|
||||||
|
const sigma_pow3 = 6 * 6 * 6 / 29 / 29 / 29;
|
||||||
|
|
||||||
|
// 1-normalized CIE XYZ with D65 to L*a*b*
|
||||||
|
// See https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB
|
||||||
|
function xyz2lab(xyz) {
|
||||||
|
const x = xyz[0] / 0.950489;
|
||||||
|
const y = xyz[1];
|
||||||
|
const z = xyz[2] / 1.088840;
|
||||||
|
const fx = x > sigma_pow3 ? x ** (1 / 3) : x / 3 / sigma_pow2 + 4 / 29;
|
||||||
|
const fy = y > sigma_pow3 ? y ** (1 / 3) : y / 3 / sigma_pow2 + 4 / 29;
|
||||||
|
const fz = z > sigma_pow3 ? z ** (1 / 3) : z / 3 / sigma_pow2 + 4 / 29;
|
||||||
|
const l = 116 * fy - 16;
|
||||||
|
const a = 500 * (fx - fy);
|
||||||
|
const b = 200 * (fy - fz);
|
||||||
|
return [l, a, b];
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.compare = compare;
|
||||||
|
var _colorUtils = require("./colorUtils");
|
||||||
|
var _imageChannel = require("./imageChannel");
|
||||||
|
var _stats = require("./stats");
|
||||||
|
/**
|
||||||
|
* 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 SSIM_WINDOW_RADIUS = 15;
|
||||||
|
const VARIANCE_WINDOW_RADIUS = 1;
|
||||||
|
function drawPixel(width, data, x, y, r, g, b) {
|
||||||
|
const idx = (y * width + x) * 4;
|
||||||
|
data[idx + 0] = r;
|
||||||
|
data[idx + 1] = g;
|
||||||
|
data[idx + 2] = b;
|
||||||
|
data[idx + 3] = 255;
|
||||||
|
}
|
||||||
|
function compare(actual, expected, diff, width, height, options = {}) {
|
||||||
|
const {
|
||||||
|
maxColorDeltaE94 = 1.0
|
||||||
|
} = options;
|
||||||
|
const paddingSize = Math.max(VARIANCE_WINDOW_RADIUS, SSIM_WINDOW_RADIUS);
|
||||||
|
const paddingColorEven = [255, 0, 255];
|
||||||
|
const paddingColorOdd = [0, 255, 0];
|
||||||
|
const [r1, g1, b1] = _imageChannel.ImageChannel.intoRGB(width, height, expected, {
|
||||||
|
paddingSize,
|
||||||
|
paddingColorEven,
|
||||||
|
paddingColorOdd
|
||||||
|
});
|
||||||
|
const [r2, g2, b2] = _imageChannel.ImageChannel.intoRGB(width, height, actual, {
|
||||||
|
paddingSize,
|
||||||
|
paddingColorEven,
|
||||||
|
paddingColorOdd
|
||||||
|
});
|
||||||
|
const noop = (x, y) => {};
|
||||||
|
const drawRedPixel = diff ? (x, y) => drawPixel(width, diff, x - paddingSize, y - paddingSize, 255, 0, 0) : noop;
|
||||||
|
const drawYellowPixel = diff ? (x, y) => drawPixel(width, diff, x - paddingSize, y - paddingSize, 255, 255, 0) : noop;
|
||||||
|
const drawGrayPixel = diff ? (x, y) => {
|
||||||
|
const gray = (0, _colorUtils.rgb2gray)(r1.get(x, y), g1.get(x, y), b1.get(x, y));
|
||||||
|
const value = (0, _colorUtils.blendWithWhite)(gray, 0.1);
|
||||||
|
drawPixel(width, diff, x - paddingSize, y - paddingSize, value, value, value);
|
||||||
|
} : noop;
|
||||||
|
let fastR, fastG, fastB;
|
||||||
|
let diffCount = 0;
|
||||||
|
for (let y = paddingSize; y < r1.height - paddingSize; ++y) {
|
||||||
|
for (let x = paddingSize; x < r1.width - paddingSize; ++x) {
|
||||||
|
// Fast-path: equal pixels.
|
||||||
|
if (r1.get(x, y) === r2.get(x, y) && g1.get(x, y) === g2.get(x, y) && b1.get(x, y) === b2.get(x, y)) {
|
||||||
|
drawGrayPixel(x, y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare pixel colors using the dE94 color difference formulae.
|
||||||
|
// The dE94 is normalized so that the value of 1.0 is the "just-noticeable-difference".
|
||||||
|
// Color difference below 1.0 is not noticeable to a human eye, so we can disregard it.
|
||||||
|
// See https://en.wikipedia.org/wiki/Color_difference
|
||||||
|
const delta = (0, _colorUtils.colorDeltaE94)([r1.get(x, y), g1.get(x, y), b1.get(x, y)], [r2.get(x, y), g2.get(x, y), b2.get(x, y)]);
|
||||||
|
if (delta <= maxColorDeltaE94) {
|
||||||
|
drawGrayPixel(x, y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!fastR || !fastG || !fastB) {
|
||||||
|
fastR = new _stats.FastStats(r1, r2);
|
||||||
|
fastG = new _stats.FastStats(g1, g2);
|
||||||
|
fastB = new _stats.FastStats(b1, b2);
|
||||||
|
}
|
||||||
|
const [varX1, varY1] = r1.boundXY(x - VARIANCE_WINDOW_RADIUS, y - VARIANCE_WINDOW_RADIUS);
|
||||||
|
const [varX2, varY2] = r1.boundXY(x + VARIANCE_WINDOW_RADIUS, y + VARIANCE_WINDOW_RADIUS);
|
||||||
|
const var1 = fastR.varianceC1(varX1, varY1, varX2, varY2) + fastG.varianceC1(varX1, varY1, varX2, varY2) + fastB.varianceC1(varX1, varY1, varX2, varY2);
|
||||||
|
const var2 = fastR.varianceC2(varX1, varY1, varX2, varY2) + fastG.varianceC2(varX1, varY1, varX2, varY2) + fastB.varianceC2(varX1, varY1, varX2, varY2);
|
||||||
|
// if this pixel is a part of a flood fill of a 3x3 square of either of the images, then it cannot be
|
||||||
|
// anti-aliasing pixel so it must be a pixel difference.
|
||||||
|
if (var1 === 0 || var2 === 0) {
|
||||||
|
drawRedPixel(x, y);
|
||||||
|
++diffCount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const [ssimX1, ssimY1] = r1.boundXY(x - SSIM_WINDOW_RADIUS, y - SSIM_WINDOW_RADIUS);
|
||||||
|
const [ssimX2, ssimY2] = r1.boundXY(x + SSIM_WINDOW_RADIUS, y + SSIM_WINDOW_RADIUS);
|
||||||
|
const ssimRGB = ((0, _stats.ssim)(fastR, ssimX1, ssimY1, ssimX2, ssimY2) + (0, _stats.ssim)(fastG, ssimX1, ssimY1, ssimX2, ssimY2) + (0, _stats.ssim)(fastB, ssimX1, ssimY1, ssimX2, ssimY2)) / 3.0;
|
||||||
|
const isAntialiassed = ssimRGB >= 0.99;
|
||||||
|
if (isAntialiassed) {
|
||||||
|
drawYellowPixel(x, y);
|
||||||
|
} else {
|
||||||
|
drawRedPixel(x, y);
|
||||||
|
++diffCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diffCount;
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.ImageChannel = void 0;
|
||||||
|
var _colorUtils = require("./colorUtils");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ImageChannel {
|
||||||
|
static intoRGB(width, height, data, options = {}) {
|
||||||
|
const {
|
||||||
|
paddingSize = 0,
|
||||||
|
paddingColorOdd = [255, 0, 255],
|
||||||
|
paddingColorEven = [0, 255, 0]
|
||||||
|
} = options;
|
||||||
|
const newWidth = width + 2 * paddingSize;
|
||||||
|
const newHeight = height + 2 * paddingSize;
|
||||||
|
const r = new Uint8Array(newWidth * newHeight);
|
||||||
|
const g = new Uint8Array(newWidth * newHeight);
|
||||||
|
const b = new Uint8Array(newWidth * newHeight);
|
||||||
|
for (let y = 0; y < newHeight; ++y) {
|
||||||
|
for (let x = 0; x < newWidth; ++x) {
|
||||||
|
const index = y * newWidth + x;
|
||||||
|
if (y >= paddingSize && y < newHeight - paddingSize && x >= paddingSize && x < newWidth - paddingSize) {
|
||||||
|
const offset = ((y - paddingSize) * width + (x - paddingSize)) * 4;
|
||||||
|
const alpha = data[offset + 3] === 255 ? 1 : data[offset + 3] / 255;
|
||||||
|
r[index] = (0, _colorUtils.blendWithWhite)(data[offset], alpha);
|
||||||
|
g[index] = (0, _colorUtils.blendWithWhite)(data[offset + 1], alpha);
|
||||||
|
b[index] = (0, _colorUtils.blendWithWhite)(data[offset + 2], alpha);
|
||||||
|
} else {
|
||||||
|
const color = (y + x) % 2 === 0 ? paddingColorEven : paddingColorOdd;
|
||||||
|
r[index] = color[0];
|
||||||
|
g[index] = color[1];
|
||||||
|
b[index] = color[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [new ImageChannel(newWidth, newHeight, r), new ImageChannel(newWidth, newHeight, g), new ImageChannel(newWidth, newHeight, b)];
|
||||||
|
}
|
||||||
|
constructor(width, height, data) {
|
||||||
|
this.data = void 0;
|
||||||
|
this.width = void 0;
|
||||||
|
this.height = void 0;
|
||||||
|
this.data = data;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
get(x, y) {
|
||||||
|
return this.data[y * this.width + x];
|
||||||
|
}
|
||||||
|
boundXY(x, y) {
|
||||||
|
return [Math.min(Math.max(x, 0), this.width - 1), Math.min(Math.max(y, 0), this.height - 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ImageChannel = ImageChannel;
|
@ -0,0 +1,102 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.FastStats = void 0;
|
||||||
|
exports.ssim = ssim;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Image channel has a 8-bit depth.
|
||||||
|
const DYNAMIC_RANGE = 2 ** 8 - 1;
|
||||||
|
function ssim(stats, x1, y1, x2, y2) {
|
||||||
|
const mean1 = stats.meanC1(x1, y1, x2, y2);
|
||||||
|
const mean2 = stats.meanC2(x1, y1, x2, y2);
|
||||||
|
const var1 = stats.varianceC1(x1, y1, x2, y2);
|
||||||
|
const var2 = stats.varianceC2(x1, y1, x2, y2);
|
||||||
|
const cov = stats.covariance(x1, y1, x2, y2);
|
||||||
|
const c1 = (0.01 * DYNAMIC_RANGE) ** 2;
|
||||||
|
const c2 = (0.03 * DYNAMIC_RANGE) ** 2;
|
||||||
|
return (2 * mean1 * mean2 + c1) * (2 * cov + c2) / (mean1 ** 2 + mean2 ** 2 + c1) / (var1 + var2 + c2);
|
||||||
|
}
|
||||||
|
class FastStats {
|
||||||
|
constructor(c1, c2) {
|
||||||
|
this.c1 = void 0;
|
||||||
|
this.c2 = void 0;
|
||||||
|
this._partialSumC1 = void 0;
|
||||||
|
this._partialSumC2 = void 0;
|
||||||
|
this._partialSumMult = void 0;
|
||||||
|
this._partialSumSq1 = void 0;
|
||||||
|
this._partialSumSq2 = void 0;
|
||||||
|
this.c1 = c1;
|
||||||
|
this.c2 = c2;
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
} = c1;
|
||||||
|
this._partialSumC1 = new Array(width * height);
|
||||||
|
this._partialSumC2 = new Array(width * height);
|
||||||
|
this._partialSumSq1 = new Array(width * height);
|
||||||
|
this._partialSumSq2 = new Array(width * height);
|
||||||
|
this._partialSumMult = new Array(width * height);
|
||||||
|
const recalc = (mx, idx, initial, x, y) => {
|
||||||
|
mx[idx] = initial;
|
||||||
|
if (y > 0) mx[idx] += mx[(y - 1) * width + x];
|
||||||
|
if (x > 0) mx[idx] += mx[y * width + x - 1];
|
||||||
|
if (x > 0 && y > 0) mx[idx] -= mx[(y - 1) * width + x - 1];
|
||||||
|
};
|
||||||
|
for (let y = 0; y < height; ++y) {
|
||||||
|
for (let x = 0; x < width; ++x) {
|
||||||
|
const idx = y * width + x;
|
||||||
|
recalc(this._partialSumC1, idx, this.c1.data[idx], x, y);
|
||||||
|
recalc(this._partialSumC2, idx, this.c2.data[idx], x, y);
|
||||||
|
recalc(this._partialSumSq1, idx, this.c1.data[idx] * this.c1.data[idx], x, y);
|
||||||
|
recalc(this._partialSumSq2, idx, this.c2.data[idx] * this.c2.data[idx], x, y);
|
||||||
|
recalc(this._partialSumMult, idx, this.c1.data[idx] * this.c2.data[idx], x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_sum(partialSum, x1, y1, x2, y2) {
|
||||||
|
const width = this.c1.width;
|
||||||
|
let result = partialSum[y2 * width + x2];
|
||||||
|
if (y1 > 0) result -= partialSum[(y1 - 1) * width + x2];
|
||||||
|
if (x1 > 0) result -= partialSum[y2 * width + x1 - 1];
|
||||||
|
if (x1 > 0 && y1 > 0) result += partialSum[(y1 - 1) * width + x1 - 1];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
meanC1(x1, y1, x2, y2) {
|
||||||
|
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
|
||||||
|
return this._sum(this._partialSumC1, x1, y1, x2, y2) / N;
|
||||||
|
}
|
||||||
|
meanC2(x1, y1, x2, y2) {
|
||||||
|
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
|
||||||
|
return this._sum(this._partialSumC2, x1, y1, x2, y2) / N;
|
||||||
|
}
|
||||||
|
varianceC1(x1, y1, x2, y2) {
|
||||||
|
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
|
||||||
|
return (this._sum(this._partialSumSq1, x1, y1, x2, y2) - this._sum(this._partialSumC1, x1, y1, x2, y2) ** 2 / N) / N;
|
||||||
|
}
|
||||||
|
varianceC2(x1, y1, x2, y2) {
|
||||||
|
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
|
||||||
|
return (this._sum(this._partialSumSq2, x1, y1, x2, y2) - this._sum(this._partialSumC2, x1, y1, x2, y2) ** 2 / N) / N;
|
||||||
|
}
|
||||||
|
covariance(x1, y1, x2, y2) {
|
||||||
|
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
|
||||||
|
return (this._sum(this._partialSumMult, x1, y1, x2, y2) - this._sum(this._partialSumC1, x1, y1, x2, y2) * this._sum(this._partialSumC2, x1, y1, x2, y2) / N) / N;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.FastStats = FastStats;
|
@ -0,0 +1,53 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.createInProcessPlaywright = createInProcessPlaywright;
|
||||||
|
var _server = require("./server");
|
||||||
|
var _connection = require("./client/connection");
|
||||||
|
var _browserServerImpl = require("./browserServerImpl");
|
||||||
|
var _androidServerImpl = require("./androidServerImpl");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function createInProcessPlaywright() {
|
||||||
|
const playwright = (0, _server.createPlaywright)({
|
||||||
|
sdkLanguage: process.env.PW_LANG_NAME || 'javascript'
|
||||||
|
});
|
||||||
|
const clientConnection = new _connection.Connection(undefined, undefined);
|
||||||
|
const dispatcherConnection = new _server.DispatcherConnection(true /* local */);
|
||||||
|
|
||||||
|
// Dispatch synchronously at first.
|
||||||
|
dispatcherConnection.onmessage = message => clientConnection.dispatch(message);
|
||||||
|
clientConnection.onmessage = message => dispatcherConnection.dispatch(message);
|
||||||
|
const rootScope = new _server.RootDispatcher(dispatcherConnection);
|
||||||
|
|
||||||
|
// Initialize Playwright channel.
|
||||||
|
new _server.PlaywrightDispatcher(rootScope, playwright);
|
||||||
|
const playwrightAPI = clientConnection.getObjectWithKnownName('Playwright');
|
||||||
|
playwrightAPI.chromium._serverLauncher = new _browserServerImpl.BrowserServerLauncherImpl('chromium');
|
||||||
|
playwrightAPI.firefox._serverLauncher = new _browserServerImpl.BrowserServerLauncherImpl('firefox');
|
||||||
|
playwrightAPI.webkit._serverLauncher = new _browserServerImpl.BrowserServerLauncherImpl('webkit');
|
||||||
|
playwrightAPI._android._serverLauncher = new _androidServerImpl.AndroidServerLauncherImpl();
|
||||||
|
|
||||||
|
// Switch to async dispatch after we got Playwright object.
|
||||||
|
dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message));
|
||||||
|
clientConnection.onmessage = message => setImmediate(() => dispatcherConnection.dispatch(message));
|
||||||
|
clientConnection.toImpl = x => x ? dispatcherConnection._dispatchers.get(x._guid)._object : dispatcherConnection._dispatchers.get('');
|
||||||
|
playwrightAPI._toImpl = clientConnection.toImpl;
|
||||||
|
return playwrightAPI;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var _inProcessFactory = require("./inProcessFactory");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = (0, _inProcessFactory.createInProcessPlaywright)();
|
@ -0,0 +1,68 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.start = start;
|
||||||
|
var _connection = require("./client/connection");
|
||||||
|
var _transport = require("./protocol/transport");
|
||||||
|
var childProcess = _interopRequireWildcard(require("child_process"));
|
||||||
|
var path = _interopRequireWildcard(require("path"));
|
||||||
|
var _manualPromise = require("./utils/manualPromise");
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function start(env = {}) {
|
||||||
|
const client = new PlaywrightClient(env);
|
||||||
|
const playwright = await client._playwright;
|
||||||
|
playwright.driverProcess = client._driverProcess;
|
||||||
|
return {
|
||||||
|
playwright,
|
||||||
|
stop: () => client.stop()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
class PlaywrightClient {
|
||||||
|
constructor(env) {
|
||||||
|
this._playwright = void 0;
|
||||||
|
this._driverProcess = void 0;
|
||||||
|
this._closePromise = new _manualPromise.ManualPromise();
|
||||||
|
this._driverProcess = childProcess.fork(path.join(__dirname, 'cli', 'cli.js'), ['run-driver'], {
|
||||||
|
stdio: 'pipe',
|
||||||
|
detached: true,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
...env
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._driverProcess.unref();
|
||||||
|
this._driverProcess.stderr.on('data', data => process.stderr.write(data));
|
||||||
|
const connection = new _connection.Connection(undefined, undefined);
|
||||||
|
connection.markAsRemote();
|
||||||
|
const transport = new _transport.PipeTransport(this._driverProcess.stdin, this._driverProcess.stdout);
|
||||||
|
connection.onmessage = message => transport.send(JSON.stringify(message));
|
||||||
|
transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
||||||
|
transport.onclose = () => this._closePromise.resolve();
|
||||||
|
this._playwright = connection.initializePlaywright();
|
||||||
|
}
|
||||||
|
async stop() {
|
||||||
|
this._driverProcess.stdin.destroy();
|
||||||
|
this._driverProcess.stdout.destroy();
|
||||||
|
this._driverProcess.stderr.destroy();
|
||||||
|
await this._closePromise;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.slowMoActions = exports.pausesBeforeInputActions = exports.commandsWithTracingSnapshots = void 0;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file is generated by generate_channels.js, do not edit manually.
|
||||||
|
|
||||||
|
const slowMoActions = new Set(['Page.goBack', 'Page.goForward', 'Page.reload', 'Page.keyboardDown', 'Page.keyboardUp', 'Page.keyboardInsertText', 'Page.keyboardType', 'Page.keyboardPress', 'Page.mouseMove', 'Page.mouseDown', 'Page.mouseUp', 'Page.mouseClick', 'Page.mouseWheel', 'Page.touchscreenTap', 'Frame.blur', 'Frame.check', 'Frame.click', 'Frame.dragAndDrop', 'Frame.dblclick', 'Frame.dispatchEvent', 'Frame.fill', 'Frame.focus', 'Frame.goto', 'Frame.hover', 'Frame.press', 'Frame.selectOption', 'Frame.setInputFiles', 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.type', 'Frame.uncheck', 'ElementHandle.check', 'ElementHandle.click', 'ElementHandle.dblclick', 'ElementHandle.dispatchEvent', 'ElementHandle.fill', 'ElementHandle.focus', 'ElementHandle.hover', 'ElementHandle.press', 'ElementHandle.scrollIntoViewIfNeeded', 'ElementHandle.selectOption', 'ElementHandle.selectText', 'ElementHandle.setInputFiles', 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.type', 'ElementHandle.uncheck']);
|
||||||
|
exports.slowMoActions = slowMoActions;
|
||||||
|
const commandsWithTracingSnapshots = new Set(['EventTarget.waitForEventInfo', 'BrowserContext.waitForEventInfo', 'Page.waitForEventInfo', 'WebSocket.waitForEventInfo', 'ElectronApplication.waitForEventInfo', 'AndroidDevice.waitForEventInfo', 'Page.emulateMedia', 'Page.goBack', 'Page.goForward', 'Page.reload', 'Page.expectScreenshot', 'Page.screenshot', 'Page.setViewportSize', 'Page.keyboardDown', 'Page.keyboardUp', 'Page.keyboardInsertText', 'Page.keyboardType', 'Page.keyboardPress', 'Page.mouseMove', 'Page.mouseDown', 'Page.mouseUp', 'Page.mouseClick', 'Page.mouseWheel', 'Page.touchscreenTap', 'Frame.evalOnSelector', 'Frame.evalOnSelectorAll', 'Frame.addScriptTag', 'Frame.addStyleTag', 'Frame.blur', 'Frame.check', 'Frame.click', 'Frame.dragAndDrop', 'Frame.dblclick', 'Frame.dispatchEvent', 'Frame.evaluateExpression', 'Frame.evaluateExpressionHandle', 'Frame.fill', 'Frame.focus', 'Frame.getAttribute', 'Frame.goto', 'Frame.hover', 'Frame.innerHTML', 'Frame.innerText', 'Frame.inputValue', 'Frame.isChecked', 'Frame.isDisabled', 'Frame.isEnabled', 'Frame.isHidden', 'Frame.isVisible', 'Frame.isEditable', 'Frame.press', 'Frame.selectOption', 'Frame.setContent', 'Frame.setInputFiles', 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.textContent', 'Frame.type', 'Frame.uncheck', 'Frame.waitForTimeout', 'Frame.waitForFunction', 'Frame.waitForSelector', 'Frame.expect', 'JSHandle.evaluateExpression', 'ElementHandle.evaluateExpression', 'JSHandle.evaluateExpressionHandle', 'ElementHandle.evaluateExpressionHandle', 'ElementHandle.evalOnSelector', 'ElementHandle.evalOnSelectorAll', 'ElementHandle.check', 'ElementHandle.click', 'ElementHandle.dblclick', 'ElementHandle.dispatchEvent', 'ElementHandle.fill', 'ElementHandle.focus', 'ElementHandle.hover', 'ElementHandle.innerHTML', 'ElementHandle.innerText', 'ElementHandle.inputValue', 'ElementHandle.isChecked', 'ElementHandle.isDisabled', 'ElementHandle.isEditable', 'ElementHandle.isEnabled', 'ElementHandle.isHidden', 'ElementHandle.isVisible', 'ElementHandle.press', 'ElementHandle.screenshot', 'ElementHandle.scrollIntoViewIfNeeded', 'ElementHandle.selectOption', 'ElementHandle.selectText', 'ElementHandle.setInputFiles', 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.textContent', 'ElementHandle.type', 'ElementHandle.uncheck', 'ElementHandle.waitForElementState', 'ElementHandle.waitForSelector']);
|
||||||
|
exports.commandsWithTracingSnapshots = commandsWithTracingSnapshots;
|
||||||
|
const pausesBeforeInputActions = new Set(['Frame.check', 'Frame.click', 'Frame.dragAndDrop', 'Frame.dblclick', 'Frame.fill', 'Frame.hover', 'Frame.press', 'Frame.selectOption', 'Frame.setInputFiles', 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.type', 'Frame.uncheck', 'ElementHandle.check', 'ElementHandle.click', 'ElementHandle.dblclick', 'ElementHandle.fill', 'ElementHandle.hover', 'ElementHandle.press', 'ElementHandle.selectOption', 'ElementHandle.setInputFiles', 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.type', 'ElementHandle.uncheck']);
|
||||||
|
exports.pausesBeforeInputActions = pausesBeforeInputActions;
|
@ -0,0 +1,204 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.parseError = parseError;
|
||||||
|
exports.parseSerializedValue = parseSerializedValue;
|
||||||
|
exports.serializeError = serializeError;
|
||||||
|
exports.serializeValue = serializeValue;
|
||||||
|
var _errors = require("../common/errors");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function serializeError(e) {
|
||||||
|
if (isError(e)) return {
|
||||||
|
error: {
|
||||||
|
message: e.message,
|
||||||
|
stack: e.stack,
|
||||||
|
name: e.name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
value: serializeValue(e, value => ({
|
||||||
|
fallThrough: value
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function parseError(error) {
|
||||||
|
if (!error.error) {
|
||||||
|
if (error.value === undefined) throw new Error('Serialized error must have either an error or a value');
|
||||||
|
return parseSerializedValue(error.value, undefined);
|
||||||
|
}
|
||||||
|
if (error.error.name === 'TimeoutError') {
|
||||||
|
const e = new _errors.TimeoutError(error.error.message);
|
||||||
|
e.stack = error.error.stack || '';
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
const e = new Error(error.error.message);
|
||||||
|
e.stack = error.error.stack || '';
|
||||||
|
e.name = error.error.name;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
function parseSerializedValue(value, handles) {
|
||||||
|
return innerParseSerializedValue(value, handles, new Map());
|
||||||
|
}
|
||||||
|
function innerParseSerializedValue(value, handles, refs) {
|
||||||
|
if (value.ref !== undefined) return refs.get(value.ref);
|
||||||
|
if (value.n !== undefined) return value.n;
|
||||||
|
if (value.s !== undefined) return value.s;
|
||||||
|
if (value.b !== undefined) return value.b;
|
||||||
|
if (value.v !== undefined) {
|
||||||
|
if (value.v === 'undefined') return undefined;
|
||||||
|
if (value.v === 'null') return null;
|
||||||
|
if (value.v === 'NaN') return NaN;
|
||||||
|
if (value.v === 'Infinity') return Infinity;
|
||||||
|
if (value.v === '-Infinity') return -Infinity;
|
||||||
|
if (value.v === '-0') return -0;
|
||||||
|
}
|
||||||
|
if (value.d !== undefined) return new Date(value.d);
|
||||||
|
if (value.u !== undefined) return new URL(value.u);
|
||||||
|
if (value.bi !== undefined) return BigInt(value.bi);
|
||||||
|
if (value.r !== undefined) return new RegExp(value.r.p, value.r.f);
|
||||||
|
if (value.a !== undefined) {
|
||||||
|
const result = [];
|
||||||
|
refs.set(value.id, result);
|
||||||
|
for (const v of value.a) result.push(innerParseSerializedValue(v, handles, refs));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (value.o !== undefined) {
|
||||||
|
const result = {};
|
||||||
|
refs.set(value.id, result);
|
||||||
|
for (const {
|
||||||
|
k,
|
||||||
|
v
|
||||||
|
} of value.o) result[k] = innerParseSerializedValue(v, handles, refs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (value.h !== undefined) {
|
||||||
|
if (handles === undefined) throw new Error('Unexpected handle');
|
||||||
|
return handles[value.h];
|
||||||
|
}
|
||||||
|
throw new Error('Unexpected value');
|
||||||
|
}
|
||||||
|
function serializeValue(value, handleSerializer) {
|
||||||
|
return innerSerializeValue(value, handleSerializer, {
|
||||||
|
lastId: 0,
|
||||||
|
visited: new Map()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function innerSerializeValue(value, handleSerializer, visitorInfo) {
|
||||||
|
const handle = handleSerializer(value);
|
||||||
|
if ('fallThrough' in handle) value = handle.fallThrough;else return handle;
|
||||||
|
if (typeof value === 'symbol') return {
|
||||||
|
v: 'undefined'
|
||||||
|
};
|
||||||
|
if (Object.is(value, undefined)) return {
|
||||||
|
v: 'undefined'
|
||||||
|
};
|
||||||
|
if (Object.is(value, null)) return {
|
||||||
|
v: 'null'
|
||||||
|
};
|
||||||
|
if (Object.is(value, NaN)) return {
|
||||||
|
v: 'NaN'
|
||||||
|
};
|
||||||
|
if (Object.is(value, Infinity)) return {
|
||||||
|
v: 'Infinity'
|
||||||
|
};
|
||||||
|
if (Object.is(value, -Infinity)) return {
|
||||||
|
v: '-Infinity'
|
||||||
|
};
|
||||||
|
if (Object.is(value, -0)) return {
|
||||||
|
v: '-0'
|
||||||
|
};
|
||||||
|
if (typeof value === 'boolean') return {
|
||||||
|
b: value
|
||||||
|
};
|
||||||
|
if (typeof value === 'number') return {
|
||||||
|
n: value
|
||||||
|
};
|
||||||
|
if (typeof value === 'string') return {
|
||||||
|
s: value
|
||||||
|
};
|
||||||
|
if (typeof value === 'bigint') return {
|
||||||
|
bi: value.toString()
|
||||||
|
};
|
||||||
|
if (isError(value)) {
|
||||||
|
const error = value;
|
||||||
|
if ('captureStackTrace' in globalThis.Error) {
|
||||||
|
// v8
|
||||||
|
return {
|
||||||
|
s: error.stack || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
s: `${error.name}: ${error.message}\n${error.stack}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isDate(value)) return {
|
||||||
|
d: value.toJSON()
|
||||||
|
};
|
||||||
|
if (isURL(value)) return {
|
||||||
|
u: value.toJSON()
|
||||||
|
};
|
||||||
|
if (isRegExp(value)) return {
|
||||||
|
r: {
|
||||||
|
p: value.source,
|
||||||
|
f: value.flags
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const id = visitorInfo.visited.get(value);
|
||||||
|
if (id) return {
|
||||||
|
ref: id
|
||||||
|
};
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const a = [];
|
||||||
|
const id = ++visitorInfo.lastId;
|
||||||
|
visitorInfo.visited.set(value, id);
|
||||||
|
for (let i = 0; i < value.length; ++i) a.push(innerSerializeValue(value[i], handleSerializer, visitorInfo));
|
||||||
|
return {
|
||||||
|
a,
|
||||||
|
id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
const o = [];
|
||||||
|
const id = ++visitorInfo.lastId;
|
||||||
|
visitorInfo.visited.set(value, id);
|
||||||
|
for (const name of Object.keys(value)) o.push({
|
||||||
|
k: name,
|
||||||
|
v: innerSerializeValue(value[name], handleSerializer, visitorInfo)
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
o,
|
||||||
|
id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error('Unexpected value');
|
||||||
|
}
|
||||||
|
function isRegExp(obj) {
|
||||||
|
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
|
||||||
|
}
|
||||||
|
function isDate(obj) {
|
||||||
|
return obj instanceof Date || Object.prototype.toString.call(obj) === '[object Date]';
|
||||||
|
}
|
||||||
|
function isURL(obj) {
|
||||||
|
return obj instanceof URL || Object.prototype.toString.call(obj) === '[object URL]';
|
||||||
|
}
|
||||||
|
function isError(obj) {
|
||||||
|
const proto = obj ? Object.getPrototypeOf(obj) : null;
|
||||||
|
return obj instanceof Error || (proto === null || proto === void 0 ? void 0 : proto.name) === 'Error' || proto && isError(proto);
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.PipeTransport = void 0;
|
||||||
|
var _utils = require("../utils");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PipeTransport {
|
||||||
|
constructor(pipeWrite, pipeRead, closeable, endian = 'le') {
|
||||||
|
this._pipeWrite = void 0;
|
||||||
|
this._data = Buffer.from([]);
|
||||||
|
this._waitForNextTask = (0, _utils.makeWaitForNextTask)();
|
||||||
|
this._closed = false;
|
||||||
|
this._bytesLeft = 0;
|
||||||
|
this.onmessage = void 0;
|
||||||
|
this.onclose = void 0;
|
||||||
|
this._endian = void 0;
|
||||||
|
this._closeableStream = void 0;
|
||||||
|
this._pipeWrite = pipeWrite;
|
||||||
|
this._endian = endian;
|
||||||
|
this._closeableStream = closeable;
|
||||||
|
pipeRead.on('data', buffer => this._dispatch(buffer));
|
||||||
|
pipeRead.on('close', () => {
|
||||||
|
this._closed = true;
|
||||||
|
if (this.onclose) this.onclose();
|
||||||
|
});
|
||||||
|
this.onmessage = undefined;
|
||||||
|
this.onclose = undefined;
|
||||||
|
}
|
||||||
|
send(message) {
|
||||||
|
if (this._closed) throw new Error('Pipe has been closed');
|
||||||
|
const data = Buffer.from(message, 'utf-8');
|
||||||
|
const dataLength = Buffer.alloc(4);
|
||||||
|
if (this._endian === 'be') dataLength.writeUInt32BE(data.length, 0);else dataLength.writeUInt32LE(data.length, 0);
|
||||||
|
this._pipeWrite.write(dataLength);
|
||||||
|
this._pipeWrite.write(data);
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
// Let it throw.
|
||||||
|
this._closeableStream.close();
|
||||||
|
}
|
||||||
|
_dispatch(buffer) {
|
||||||
|
this._data = Buffer.concat([this._data, buffer]);
|
||||||
|
while (true) {
|
||||||
|
if (!this._bytesLeft && this._data.length < 4) {
|
||||||
|
// Need more data.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!this._bytesLeft) {
|
||||||
|
this._bytesLeft = this._endian === 'be' ? this._data.readUInt32BE(0) : this._data.readUInt32LE(0);
|
||||||
|
this._data = this._data.slice(4);
|
||||||
|
}
|
||||||
|
if (!this._bytesLeft || this._data.length < this._bytesLeft) {
|
||||||
|
// Need more data.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const message = this._data.slice(0, this._bytesLeft);
|
||||||
|
this._data = this._data.slice(this._bytesLeft);
|
||||||
|
this._bytesLeft = 0;
|
||||||
|
this._waitForNextTask(() => {
|
||||||
|
if (this.onmessage) this.onmessage(message.toString('utf-8'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.PipeTransport = PipeTransport;
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,140 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.ValidationError = void 0;
|
||||||
|
exports.createMetadataValidator = createMetadataValidator;
|
||||||
|
exports.findValidator = findValidator;
|
||||||
|
exports.maybeFindValidator = maybeFindValidator;
|
||||||
|
exports.tUndefined = exports.tType = exports.tString = exports.tOptional = exports.tObject = exports.tNumber = exports.tEnum = exports.tChannel = exports.tBoolean = exports.tBinary = exports.tArray = exports.tAny = exports.scheme = void 0;
|
||||||
|
var _utils = require("../utils");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ValidationError extends Error {}
|
||||||
|
exports.ValidationError = ValidationError;
|
||||||
|
const scheme = {};
|
||||||
|
exports.scheme = scheme;
|
||||||
|
function findValidator(type, method, kind) {
|
||||||
|
const validator = maybeFindValidator(type, method, kind);
|
||||||
|
if (!validator) throw new ValidationError(`Unknown scheme for ${kind}: ${type}.${method}`);
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
function maybeFindValidator(type, method, kind) {
|
||||||
|
const schemeName = type + (kind === 'Initializer' ? '' : method[0].toUpperCase() + method.substring(1)) + kind;
|
||||||
|
return scheme[schemeName];
|
||||||
|
}
|
||||||
|
function createMetadataValidator() {
|
||||||
|
return tOptional(scheme['Metadata']);
|
||||||
|
}
|
||||||
|
const tNumber = (arg, path, context) => {
|
||||||
|
if (arg instanceof Number) return arg.valueOf();
|
||||||
|
if (typeof arg === 'number') return arg;
|
||||||
|
throw new ValidationError(`${path}: expected number, got ${typeof arg}`);
|
||||||
|
};
|
||||||
|
exports.tNumber = tNumber;
|
||||||
|
const tBoolean = (arg, path, context) => {
|
||||||
|
if (arg instanceof Boolean) return arg.valueOf();
|
||||||
|
if (typeof arg === 'boolean') return arg;
|
||||||
|
throw new ValidationError(`${path}: expected boolean, got ${typeof arg}`);
|
||||||
|
};
|
||||||
|
exports.tBoolean = tBoolean;
|
||||||
|
const tString = (arg, path, context) => {
|
||||||
|
if (arg instanceof String) return arg.valueOf();
|
||||||
|
if (typeof arg === 'string') return arg;
|
||||||
|
throw new ValidationError(`${path}: expected string, got ${typeof arg}`);
|
||||||
|
};
|
||||||
|
exports.tString = tString;
|
||||||
|
const tBinary = (arg, path, context) => {
|
||||||
|
if (context.binary === 'fromBase64') {
|
||||||
|
if (arg instanceof String) return Buffer.from(arg.valueOf(), 'base64');
|
||||||
|
if (typeof arg === 'string') return Buffer.from(arg, 'base64');
|
||||||
|
throw new ValidationError(`${path}: expected base64-encoded buffer, got ${typeof arg}`);
|
||||||
|
}
|
||||||
|
if (context.binary === 'toBase64') {
|
||||||
|
if (!(arg instanceof Buffer)) throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
|
||||||
|
return arg.toString('base64');
|
||||||
|
}
|
||||||
|
if (context.binary === 'buffer') {
|
||||||
|
if (!(arg instanceof Buffer)) throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
throw new ValidationError(`Unsupported binary behavior "${context.binary}"`);
|
||||||
|
};
|
||||||
|
exports.tBinary = tBinary;
|
||||||
|
const tUndefined = (arg, path, context) => {
|
||||||
|
if (Object.is(arg, undefined)) return arg;
|
||||||
|
throw new ValidationError(`${path}: expected undefined, got ${typeof arg}`);
|
||||||
|
};
|
||||||
|
exports.tUndefined = tUndefined;
|
||||||
|
const tAny = (arg, path, context) => {
|
||||||
|
return arg;
|
||||||
|
};
|
||||||
|
exports.tAny = tAny;
|
||||||
|
const tOptional = v => {
|
||||||
|
return (arg, path, context) => {
|
||||||
|
if (Object.is(arg, undefined)) return arg;
|
||||||
|
return v(arg, path, context);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
exports.tOptional = tOptional;
|
||||||
|
const tArray = v => {
|
||||||
|
return (arg, path, context) => {
|
||||||
|
if (!Array.isArray(arg)) throw new ValidationError(`${path}: expected array, got ${typeof arg}`);
|
||||||
|
return arg.map((x, index) => v(x, path + '[' + index + ']', context));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
exports.tArray = tArray;
|
||||||
|
const tObject = s => {
|
||||||
|
return (arg, path, context) => {
|
||||||
|
if (Object.is(arg, null)) throw new ValidationError(`${path}: expected object, got null`);
|
||||||
|
if (typeof arg !== 'object') throw new ValidationError(`${path}: expected object, got ${typeof arg}`);
|
||||||
|
const result = {};
|
||||||
|
for (const [key, v] of Object.entries(s)) {
|
||||||
|
const value = v(arg[key], path ? path + '.' + key : key, context);
|
||||||
|
if (!Object.is(value, undefined)) result[key] = value;
|
||||||
|
}
|
||||||
|
if ((0, _utils.isUnderTest)()) {
|
||||||
|
for (const [key, value] of Object.entries(arg)) {
|
||||||
|
if (key.startsWith('__testHook')) result[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
exports.tObject = tObject;
|
||||||
|
const tEnum = e => {
|
||||||
|
return (arg, path, context) => {
|
||||||
|
if (!e.includes(arg)) throw new ValidationError(`${path}: expected one of (${e.join('|')})`);
|
||||||
|
return arg;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
exports.tEnum = tEnum;
|
||||||
|
const tChannel = names => {
|
||||||
|
return (arg, path, context) => {
|
||||||
|
return context.tChannelImpl(names, arg, path, context);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
exports.tChannel = tChannel;
|
||||||
|
const tType = name => {
|
||||||
|
return (arg, path, context) => {
|
||||||
|
const v = scheme[name];
|
||||||
|
if (!v) throw new ValidationError(path + ': unknown type "' + name + '"');
|
||||||
|
return v(arg, path, context);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
exports.tType = tType;
|
@ -0,0 +1,251 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.PlaywrightConnection = void 0;
|
||||||
|
var _server = require("../server");
|
||||||
|
var _browser = require("../server/browser");
|
||||||
|
var _instrumentation = require("../server/instrumentation");
|
||||||
|
var _socksProxy = require("../common/socksProxy");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
var _android = require("../server/android/android");
|
||||||
|
var _debugControllerDispatcher = require("../server/dispatchers/debugControllerDispatcher");
|
||||||
|
var _debugLogger = require("../common/debugLogger");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PlaywrightConnection {
|
||||||
|
constructor(lock, clientType, ws, options, preLaunched, id, onClose) {
|
||||||
|
this._ws = void 0;
|
||||||
|
this._onClose = void 0;
|
||||||
|
this._dispatcherConnection = void 0;
|
||||||
|
this._cleanups = [];
|
||||||
|
this._id = void 0;
|
||||||
|
this._disconnected = false;
|
||||||
|
this._preLaunched = void 0;
|
||||||
|
this._options = void 0;
|
||||||
|
this._root = void 0;
|
||||||
|
this._profileName = void 0;
|
||||||
|
this._ws = ws;
|
||||||
|
this._preLaunched = preLaunched;
|
||||||
|
this._options = options;
|
||||||
|
options.launchOptions = filterLaunchOptions(options.launchOptions);
|
||||||
|
if (clientType === 'reuse-browser' || clientType === 'pre-launched-browser-or-android') (0, _utils.assert)(preLaunched.playwright);
|
||||||
|
if (clientType === 'pre-launched-browser-or-android') (0, _utils.assert)(preLaunched.browser || preLaunched.androidDevice);
|
||||||
|
this._onClose = onClose;
|
||||||
|
this._id = id;
|
||||||
|
this._profileName = `${new Date().toISOString()}-${clientType}`;
|
||||||
|
this._dispatcherConnection = new _server.DispatcherConnection();
|
||||||
|
this._dispatcherConnection.onmessage = async message => {
|
||||||
|
await lock;
|
||||||
|
if (ws.readyState !== ws.CLOSING) {
|
||||||
|
const messageString = JSON.stringify(message);
|
||||||
|
if (_debugLogger.debugLogger.isEnabled('server:channel')) _debugLogger.debugLogger.log('server:channel', `[${this._id}] ${(0, _utils.monotonicTime)() * 1000} SEND ► ${messageString}`);
|
||||||
|
ws.send(messageString);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ws.on('message', async message => {
|
||||||
|
await lock;
|
||||||
|
const messageString = Buffer.from(message).toString();
|
||||||
|
if (_debugLogger.debugLogger.isEnabled('server:channel')) _debugLogger.debugLogger.log('server:channel', `[${this._id}] ${(0, _utils.monotonicTime)() * 1000} ◀ RECV ${messageString}`);
|
||||||
|
this._dispatcherConnection.dispatch(JSON.parse(messageString));
|
||||||
|
});
|
||||||
|
ws.on('close', () => this._onDisconnect());
|
||||||
|
ws.on('error', error => this._onDisconnect(error));
|
||||||
|
if (clientType === 'controller') {
|
||||||
|
this._root = this._initDebugControllerMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._root = new _server.RootDispatcher(this._dispatcherConnection, async scope => {
|
||||||
|
await (0, _utils.startProfiling)();
|
||||||
|
if (clientType === 'reuse-browser') return await this._initReuseBrowsersMode(scope);
|
||||||
|
if (clientType === 'pre-launched-browser-or-android') return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope) : await this._initPreLaunchedAndroidMode(scope);
|
||||||
|
if (clientType === 'launch-browser') return await this._initLaunchBrowserMode(scope);
|
||||||
|
throw new Error('Unsupported client type: ' + clientType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _initLaunchBrowserMode(scope) {
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`);
|
||||||
|
const playwright = (0, _server.createPlaywright)({
|
||||||
|
sdkLanguage: 'javascript',
|
||||||
|
isServer: true
|
||||||
|
});
|
||||||
|
const ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
|
||||||
|
const browser = await playwright[this._options.browserName].launch((0, _instrumentation.serverSideCallMetadata)(), this._options.launchOptions);
|
||||||
|
this._cleanups.push(async () => {
|
||||||
|
for (const browser of playwright.allBrowsers()) await browser.close();
|
||||||
|
});
|
||||||
|
browser.on(_browser.Browser.Events.Disconnected, () => {
|
||||||
|
// Underlying browser did close for some reason - force disconnect the client.
|
||||||
|
this.close({
|
||||||
|
code: 1001,
|
||||||
|
reason: 'Browser closed'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return new _server.PlaywrightDispatcher(scope, playwright, ownedSocksProxy, browser);
|
||||||
|
}
|
||||||
|
async _initPreLaunchedBrowserMode(scope) {
|
||||||
|
var _this$_preLaunched$so;
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] engaged pre-launched (browser) mode`);
|
||||||
|
const playwright = this._preLaunched.playwright;
|
||||||
|
|
||||||
|
// Note: connected client owns the socks proxy and configures the pattern.
|
||||||
|
(_this$_preLaunched$so = this._preLaunched.socksProxy) === null || _this$_preLaunched$so === void 0 ? void 0 : _this$_preLaunched$so.setPattern(this._options.socksProxyPattern);
|
||||||
|
const browser = this._preLaunched.browser;
|
||||||
|
browser.on(_browser.Browser.Events.Disconnected, () => {
|
||||||
|
// Underlying browser did close for some reason - force disconnect the client.
|
||||||
|
this.close({
|
||||||
|
code: 1001,
|
||||||
|
reason: 'Browser closed'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const playwrightDispatcher = new _server.PlaywrightDispatcher(scope, playwright, this._preLaunched.socksProxy, browser);
|
||||||
|
// In pre-launched mode, keep only the pre-launched browser.
|
||||||
|
for (const b of playwright.allBrowsers()) {
|
||||||
|
if (b !== browser) await b.close();
|
||||||
|
}
|
||||||
|
this._cleanups.push(() => playwrightDispatcher.cleanup());
|
||||||
|
return playwrightDispatcher;
|
||||||
|
}
|
||||||
|
async _initPreLaunchedAndroidMode(scope) {
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] engaged pre-launched (Android) mode`);
|
||||||
|
const playwright = this._preLaunched.playwright;
|
||||||
|
const androidDevice = this._preLaunched.androidDevice;
|
||||||
|
androidDevice.on(_android.AndroidDevice.Events.Close, () => {
|
||||||
|
// Underlying browser did close for some reason - force disconnect the client.
|
||||||
|
this.close({
|
||||||
|
code: 1001,
|
||||||
|
reason: 'Android device disconnected'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const playwrightDispatcher = new _server.PlaywrightDispatcher(scope, playwright, undefined, undefined, androidDevice);
|
||||||
|
this._cleanups.push(() => playwrightDispatcher.cleanup());
|
||||||
|
return playwrightDispatcher;
|
||||||
|
}
|
||||||
|
_initDebugControllerMode() {
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] engaged reuse controller mode`);
|
||||||
|
const playwright = this._preLaunched.playwright;
|
||||||
|
// Always create new instance based on the reused Playwright instance.
|
||||||
|
return new _debugControllerDispatcher.DebugControllerDispatcher(this._dispatcherConnection, playwright.debugController);
|
||||||
|
}
|
||||||
|
async _initReuseBrowsersMode(scope) {
|
||||||
|
// Note: reuse browser mode does not support socks proxy, because
|
||||||
|
// clients come and go, while the browser stays the same.
|
||||||
|
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] engaged reuse browsers mode for ${this._options.browserName}`);
|
||||||
|
const playwright = this._preLaunched.playwright;
|
||||||
|
const requestedOptions = launchOptionsHash(this._options.launchOptions);
|
||||||
|
let browser = playwright.allBrowsers().find(b => {
|
||||||
|
if (b.options.name !== this._options.browserName) return false;
|
||||||
|
const existingOptions = launchOptionsHash(b.options.originalLaunchOptions);
|
||||||
|
return existingOptions === requestedOptions;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close remaining browsers of this type+channel. Keep different browser types for the speed.
|
||||||
|
for (const b of playwright.allBrowsers()) {
|
||||||
|
if (b === browser) continue;
|
||||||
|
if (b.options.name === this._options.browserName && b.options.channel === this._options.launchOptions.channel) await b.close();
|
||||||
|
}
|
||||||
|
if (!browser) {
|
||||||
|
browser = await playwright[this._options.browserName || 'chromium'].launch((0, _instrumentation.serverSideCallMetadata)(), {
|
||||||
|
...this._options.launchOptions,
|
||||||
|
headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS
|
||||||
|
});
|
||||||
|
browser.on(_browser.Browser.Events.Disconnected, () => {
|
||||||
|
// Underlying browser did close for some reason - force disconnect the client.
|
||||||
|
this.close({
|
||||||
|
code: 1001,
|
||||||
|
reason: 'Browser closed'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._cleanups.push(async () => {
|
||||||
|
// Don't close the pages so that user could debug them,
|
||||||
|
// but close all the empty browsers and contexts to clean up.
|
||||||
|
for (const browser of playwright.allBrowsers()) {
|
||||||
|
for (const context of browser.contexts()) {
|
||||||
|
if (!context.pages().length) await context.close((0, _instrumentation.serverSideCallMetadata)());else await context.stopPendingOperations();
|
||||||
|
}
|
||||||
|
if (!browser.contexts()) await browser.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const playwrightDispatcher = new _server.PlaywrightDispatcher(scope, playwright, undefined, browser);
|
||||||
|
return playwrightDispatcher;
|
||||||
|
}
|
||||||
|
async _createOwnedSocksProxy(playwright) {
|
||||||
|
if (!this._options.socksProxyPattern) return;
|
||||||
|
const socksProxy = new _socksProxy.SocksProxy();
|
||||||
|
socksProxy.setPattern(this._options.socksProxyPattern);
|
||||||
|
playwright.options.socksProxyPort = await socksProxy.listen(0);
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] started socks proxy on port ${playwright.options.socksProxyPort}`);
|
||||||
|
this._cleanups.push(() => socksProxy.close());
|
||||||
|
return socksProxy;
|
||||||
|
}
|
||||||
|
async _onDisconnect(error) {
|
||||||
|
this._disconnected = true;
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] disconnected. error: ${error}`);
|
||||||
|
this._root._dispose();
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] starting cleanup`);
|
||||||
|
for (const cleanup of this._cleanups) await cleanup().catch(() => {});
|
||||||
|
await (0, _utils.stopProfiling)(this._profileName);
|
||||||
|
this._onClose();
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] finished cleanup`);
|
||||||
|
}
|
||||||
|
async close(reason) {
|
||||||
|
if (this._disconnected) return;
|
||||||
|
_debugLogger.debugLogger.log('server', `[${this._id}] force closing connection: ${(reason === null || reason === void 0 ? void 0 : reason.reason) || ''} (${(reason === null || reason === void 0 ? void 0 : reason.code) || 0})`);
|
||||||
|
try {
|
||||||
|
this._ws.close(reason === null || reason === void 0 ? void 0 : reason.code, reason === null || reason === void 0 ? void 0 : reason.reason);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.PlaywrightConnection = PlaywrightConnection;
|
||||||
|
function launchOptionsHash(options) {
|
||||||
|
const copy = {
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
for (const k of Object.keys(copy)) {
|
||||||
|
const key = k;
|
||||||
|
if (copy[key] === defaultLaunchOptions[key]) delete copy[key];
|
||||||
|
}
|
||||||
|
for (const key of optionsThatAllowBrowserReuse) delete copy[key];
|
||||||
|
return JSON.stringify(copy);
|
||||||
|
}
|
||||||
|
function filterLaunchOptions(options) {
|
||||||
|
return {
|
||||||
|
channel: options.channel,
|
||||||
|
args: options.args,
|
||||||
|
ignoreAllDefaultArgs: options.ignoreAllDefaultArgs,
|
||||||
|
ignoreDefaultArgs: options.ignoreDefaultArgs,
|
||||||
|
timeout: options.timeout,
|
||||||
|
headless: options.headless,
|
||||||
|
proxy: options.proxy,
|
||||||
|
chromiumSandbox: options.chromiumSandbox,
|
||||||
|
firefoxUserPrefs: options.firefoxUserPrefs,
|
||||||
|
slowMo: options.slowMo,
|
||||||
|
executablePath: (0, _utils.isUnderTest)() ? options.executablePath : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const defaultLaunchOptions = {
|
||||||
|
ignoreAllDefaultArgs: false,
|
||||||
|
handleSIGINT: false,
|
||||||
|
handleSIGTERM: false,
|
||||||
|
handleSIGHUP: false,
|
||||||
|
headless: true,
|
||||||
|
devtools: false
|
||||||
|
};
|
||||||
|
const optionsThatAllowBrowserReuse = ['headless', 'tracesDir'];
|
@ -0,0 +1,178 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Semaphore = exports.PlaywrightServer = void 0;
|
||||||
|
var _utilsBundle = require("../utilsBundle");
|
||||||
|
var _playwright = require("../server/playwright");
|
||||||
|
var _playwrightConnection = require("./playwrightConnection");
|
||||||
|
var _manualPromise = require("../utils/manualPromise");
|
||||||
|
var _debugLogger = require("../common/debugLogger");
|
||||||
|
var _utils = require("../utils");
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let lastConnectionId = 0;
|
||||||
|
const kConnectionSymbol = Symbol('kConnection');
|
||||||
|
class PlaywrightServer {
|
||||||
|
constructor(options) {
|
||||||
|
this._preLaunchedPlaywright = void 0;
|
||||||
|
this._wsServer = void 0;
|
||||||
|
this._options = void 0;
|
||||||
|
this._options = options;
|
||||||
|
if (options.preLaunchedBrowser) this._preLaunchedPlaywright = options.preLaunchedBrowser.attribution.playwright;
|
||||||
|
if (options.preLaunchedAndroidDevice) this._preLaunchedPlaywright = options.preLaunchedAndroidDevice._android.attribution.playwright;
|
||||||
|
}
|
||||||
|
async listen(port = 0) {
|
||||||
|
_debugLogger.debugLogger.log('server', `Server started at ${new Date()}`);
|
||||||
|
const server = (0, _utils.createHttpServer)((request, response) => {
|
||||||
|
if (request.method === 'GET' && request.url === '/json') {
|
||||||
|
response.setHeader('Content-Type', 'application/json');
|
||||||
|
response.end(JSON.stringify({
|
||||||
|
wsEndpointPath: this._options.path
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.end('Running');
|
||||||
|
});
|
||||||
|
server.on('error', error => _debugLogger.debugLogger.log('server', String(error)));
|
||||||
|
const wsEndpoint = await new Promise((resolve, reject) => {
|
||||||
|
server.listen(port, () => {
|
||||||
|
const address = server.address();
|
||||||
|
if (!address) {
|
||||||
|
reject(new Error('Could not bind server socket'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wsEndpoint = typeof address === 'string' ? `${address}${this._options.path}` : `ws://127.0.0.1:${address.port}${this._options.path}`;
|
||||||
|
resolve(wsEndpoint);
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
_debugLogger.debugLogger.log('server', 'Listening at ' + wsEndpoint);
|
||||||
|
this._wsServer = new _utilsBundle.wsServer({
|
||||||
|
server,
|
||||||
|
path: this._options.path
|
||||||
|
});
|
||||||
|
const browserSemaphore = new Semaphore(this._options.maxConnections);
|
||||||
|
const controllerSemaphore = new Semaphore(1);
|
||||||
|
const reuseBrowserSemaphore = new Semaphore(1);
|
||||||
|
if (process.env.PWTEST_SERVER_WS_HEADERS) {
|
||||||
|
this._wsServer.on('headers', (headers, request) => {
|
||||||
|
headers.push(process.env.PWTEST_SERVER_WS_HEADERS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._wsServer.on('connection', (ws, request) => {
|
||||||
|
const url = new URL('http://localhost' + (request.url || ''));
|
||||||
|
const browserHeader = request.headers['x-playwright-browser'];
|
||||||
|
const browserName = url.searchParams.get('browser') || (Array.isArray(browserHeader) ? browserHeader[0] : browserHeader) || null;
|
||||||
|
const proxyHeader = request.headers['x-playwright-proxy'];
|
||||||
|
const proxyValue = url.searchParams.get('proxy') || (Array.isArray(proxyHeader) ? proxyHeader[0] : proxyHeader);
|
||||||
|
const launchOptionsHeader = request.headers['x-playwright-launch-options'] || '';
|
||||||
|
const launchOptionsHeaderValue = Array.isArray(launchOptionsHeader) ? launchOptionsHeader[0] : launchOptionsHeader;
|
||||||
|
const launchOptionsParam = url.searchParams.get('launch-options');
|
||||||
|
let launchOptions = {};
|
||||||
|
try {
|
||||||
|
launchOptions = JSON.parse(launchOptionsParam || launchOptionsHeaderValue);
|
||||||
|
} catch (e) {}
|
||||||
|
const id = String(++lastConnectionId);
|
||||||
|
_debugLogger.debugLogger.log('server', `[${id}] serving connection: ${request.url}`);
|
||||||
|
|
||||||
|
// Instantiate playwright for the extension modes.
|
||||||
|
const isExtension = this._options.mode === 'extension';
|
||||||
|
if (isExtension) {
|
||||||
|
if (!this._preLaunchedPlaywright) this._preLaunchedPlaywright = (0, _playwright.createPlaywright)({
|
||||||
|
sdkLanguage: 'javascript',
|
||||||
|
isServer: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let clientType = 'launch-browser';
|
||||||
|
let semaphore = browserSemaphore;
|
||||||
|
if (isExtension && url.searchParams.has('debug-controller')) {
|
||||||
|
clientType = 'controller';
|
||||||
|
semaphore = controllerSemaphore;
|
||||||
|
} else if (isExtension) {
|
||||||
|
clientType = 'reuse-browser';
|
||||||
|
semaphore = reuseBrowserSemaphore;
|
||||||
|
} else if (this._options.mode === 'launchServer') {
|
||||||
|
clientType = 'pre-launched-browser-or-android';
|
||||||
|
semaphore = browserSemaphore;
|
||||||
|
}
|
||||||
|
const connection = new _playwrightConnection.PlaywrightConnection(semaphore.aquire(), clientType, ws, {
|
||||||
|
socksProxyPattern: proxyValue,
|
||||||
|
browserName,
|
||||||
|
launchOptions
|
||||||
|
}, {
|
||||||
|
playwright: this._preLaunchedPlaywright,
|
||||||
|
browser: this._options.preLaunchedBrowser,
|
||||||
|
androidDevice: this._options.preLaunchedAndroidDevice,
|
||||||
|
socksProxy: this._options.preLaunchedSocksProxy
|
||||||
|
}, id, () => semaphore.release());
|
||||||
|
ws[kConnectionSymbol] = connection;
|
||||||
|
});
|
||||||
|
return wsEndpoint;
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
const server = this._wsServer;
|
||||||
|
if (!server) return;
|
||||||
|
_debugLogger.debugLogger.log('server', 'closing websocket server');
|
||||||
|
const waitForClose = new Promise(f => server.close(f));
|
||||||
|
// First disconnect all remaining clients.
|
||||||
|
await Promise.all(Array.from(server.clients).map(async ws => {
|
||||||
|
const connection = ws[kConnectionSymbol];
|
||||||
|
if (connection) await connection.close();
|
||||||
|
try {
|
||||||
|
ws.terminate();
|
||||||
|
} catch (e) {}
|
||||||
|
}));
|
||||||
|
await waitForClose;
|
||||||
|
_debugLogger.debugLogger.log('server', 'closing http server');
|
||||||
|
await new Promise(f => server.options.server.close(f));
|
||||||
|
this._wsServer = undefined;
|
||||||
|
_debugLogger.debugLogger.log('server', 'closed server');
|
||||||
|
_debugLogger.debugLogger.log('server', 'closing browsers');
|
||||||
|
if (this._preLaunchedPlaywright) await Promise.all(this._preLaunchedPlaywright.allBrowsers().map(browser => browser.close()));
|
||||||
|
_debugLogger.debugLogger.log('server', 'closed browsers');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.PlaywrightServer = PlaywrightServer;
|
||||||
|
class Semaphore {
|
||||||
|
constructor(max) {
|
||||||
|
this._max = void 0;
|
||||||
|
this._aquired = 0;
|
||||||
|
this._queue = [];
|
||||||
|
this._max = max;
|
||||||
|
}
|
||||||
|
setMax(max) {
|
||||||
|
this._max = max;
|
||||||
|
}
|
||||||
|
aquire() {
|
||||||
|
const lock = new _manualPromise.ManualPromise();
|
||||||
|
this._queue.push(lock);
|
||||||
|
this._flush();
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
release() {
|
||||||
|
--this._aquired;
|
||||||
|
this._flush();
|
||||||
|
}
|
||||||
|
_flush() {
|
||||||
|
while (this._aquired < this._max && this._queue.length) {
|
||||||
|
++this._aquired;
|
||||||
|
this._queue.shift().resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Semaphore = Semaphore;
|
@ -0,0 +1,62 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.Accessibility = void 0;
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
* Modifications 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Accessibility {
|
||||||
|
constructor(getAXTree) {
|
||||||
|
this._getAXTree = void 0;
|
||||||
|
this._getAXTree = getAXTree;
|
||||||
|
}
|
||||||
|
async snapshot(options = {}) {
|
||||||
|
const {
|
||||||
|
interestingOnly = true,
|
||||||
|
root = null
|
||||||
|
} = options;
|
||||||
|
const {
|
||||||
|
tree,
|
||||||
|
needle
|
||||||
|
} = await this._getAXTree(root || undefined);
|
||||||
|
if (!interestingOnly) {
|
||||||
|
if (root) return needle && serializeTree(needle)[0];
|
||||||
|
return serializeTree(tree)[0];
|
||||||
|
}
|
||||||
|
const interestingNodes = new Set();
|
||||||
|
collectInterestingNodes(interestingNodes, tree, false);
|
||||||
|
if (root && (!needle || !interestingNodes.has(needle))) return null;
|
||||||
|
return serializeTree(needle || tree, interestingNodes)[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Accessibility = Accessibility;
|
||||||
|
function collectInterestingNodes(collection, node, insideControl) {
|
||||||
|
if (node.isInteresting(insideControl)) collection.add(node);
|
||||||
|
if (node.isLeafNode()) return;
|
||||||
|
insideControl = insideControl || node.isControl();
|
||||||
|
for (const child of node.children()) collectInterestingNodes(collection, child, insideControl);
|
||||||
|
}
|
||||||
|
function serializeTree(node, whitelistedNodes) {
|
||||||
|
const children = [];
|
||||||
|
for (const child of node.children()) children.push(...serializeTree(child, whitelistedNodes));
|
||||||
|
if (whitelistedNodes && !whitelistedNodes.has(node)) return children;
|
||||||
|
const serializedNode = node.serialize();
|
||||||
|
if (children.length) serializedNode.children = children;
|
||||||
|
return [serializedNode];
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user