blob: 90b95768f7f2ce7dca707649821e888cd3379842 [file] [log] [blame] [edit]
#!/usr/bin/env python3
# Copyright (C) 2021 The Android Open Source Project
#
# 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 script runs the Playwright tests for the UI. It can be run from the root
# directory with `./ui/run-integrationtests` and accepts the same arguments as
# `playwright test` (e.g. `./ui/run-integrationtests --workers=1
# src/test/wattson.test.ts`).
#
# It makes sure that the build is up to date before
import argparse
import os
import shlex
import subprocess
import sys
UI_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.dirname(UI_DIR)
# Custom Playwright Docker image name (built from ui/playwright/Dockerfile)
PLAYWRIGHT_IMAGE = 'perfetto-playwright'
PLAYWRIGHT_DOCKERFILE = os.path.join(UI_DIR, 'playwright', 'Dockerfile')
def get_playwright_version():
"""Get the installed Playwright version."""
result = subprocess.run(
['./pnpm', 'exec', 'playwright', '--version'],
capture_output=True,
text=True,
cwd=UI_DIR
)
if result.returncode != 0:
raise RuntimeError(f'Failed to get Playwright version: {result.stderr}')
version_line = result.stdout.strip()
if not version_line.startswith('Version '):
raise RuntimeError(f'Unexpected Playwright version output: {version_line}')
return version_line[len('Version '):]
def build_docker_image(version, use_sudo):
"""Build the custom Playwright Docker image with Mesa GL support."""
image_tag = f'{PLAYWRIGHT_IMAGE}:v{version}'
print(f'Building Docker image: {image_tag}')
build_cmd = [
'docker', 'build',
'--build-arg', f'PLAYWRIGHT_VERSION={version}',
'-t', image_tag,
os.path.dirname(PLAYWRIGHT_DOCKERFILE)
]
if use_sudo:
build_cmd = ['sudo'] + build_cmd
result = subprocess.run(build_cmd)
if result.returncode != 0:
raise RuntimeError(f'Failed to build Docker image')
return image_tag
def is_docker_available():
"""Check if docker is installed and accessible."""
try:
result = subprocess.run(
['docker', '--version'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
timeout=5
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def needs_sudo_for_docker():
"""Check if sudo is needed to run docker commands."""
try:
result = subprocess.run(
['docker', 'info'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
timeout=5
)
return result.returncode != 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return True
def run_build(args):
"""Run the build script to ensure we have something to test."""
build_args = []
if args.no_build:
build_args += ['--no-build']
if args.no_depscheck:
build_args += ['--no-depscheck']
return subprocess.call(['ui/build'] + build_args)
def run_native(args):
"""Run tests directly on the host machine."""
print('Warning: Running without Docker. Results may vary across environments.')
if args.rebaseline:
print('Note: Rebaselining outside Docker may cause snapshot mismatches in CI.')
print('')
# Install the chromium through playwright
subprocess.check_call(['./pnpm', 'exec', 'playwright', 'install', 'chromium'], cwd=UI_DIR)
cmd = ['./pnpm', 'exec', 'playwright', 'test']
if args.interactive:
if args.rebaseline:
print('--interactive and --rebaseline are mutually exclusive')
return 1
cmd += ['--ui']
elif args.rebaseline:
cmd += ['--update-snapshots']
if args.workers:
cmd += ['--workers', args.workers]
cmd += args.filters
env = dict(os.environ.items())
dev_server_args = []
if args.out:
out_rel_path = os.path.relpath(args.out, UI_DIR)
env['OUT_DIR'] = out_rel_path
dev_server_args += ['--out', out_rel_path]
env['DEV_SERVER_ARGS'] = ' '.join(dev_server_args)
os.chdir(UI_DIR)
os.execve(cmd[0], cmd, env)
def run_in_docker(args):
"""Run tests inside a Docker container with pre-installed Chrome."""
use_sudo = needs_sudo_for_docker()
# Get the Playwright version and build/use matching Docker image
try:
version = get_playwright_version()
image = build_docker_image(version, use_sudo)
except RuntimeError as e:
print(str(e))
return 1
# Build the playwright command (wrapped with xvfb-run for virtual display)
# First install browsers to /tmp (the only writable location in the container)
install_cmd = 'PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright ./pnpm exec playwright install chromium'
playwright_cmd_parts = [
'unbuffer', 'xvfb-run', '--auto-servernum',
'./pnpm', 'exec', 'playwright', 'test'
]
if args.rebaseline:
playwright_cmd_parts += ['--update-snapshots']
if args.workers:
playwright_cmd_parts += ['--workers', args.workers]
playwright_cmd_parts += args.filters
playwright_test_cmd = ' '.join(shlex.quote(p) for p in playwright_cmd_parts)
playwright_cmd = f'{install_cmd} && PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright {playwright_test_cmd}'
# Build dev server args (similar to run_native)
dev_server_args = []
env_args = []
if args.out:
out_rel_path = os.path.relpath(args.out, UI_DIR)
env_args += ['-e', f'OUT_DIR={out_rel_path}']
dev_server_args += ['--out', out_rel_path]
if dev_server_args:
env_args += ['-e', f'DEV_SERVER_ARGS={" ".join(dev_server_args)}']
docker_cmd = [
'docker', 'run',
'--rm', # Remove the container after it exits
'-t', # Allocate a pseudo-TTY for better output formatting
'-v', f'{REPO_ROOT}:{REPO_ROOT}', # Mount the repo root into the container
'-w', f'{REPO_ROOT}/ui', # Set working directory to /ui
'-u', f'{os.getuid()}:{os.getgid()}', # Run as current user to avoid permission issues
'-e', 'HOME=/tmp', # Chrome needs a writable home for crashpad
*env_args,
image,
'bash', '-c', playwright_cmd
]
if use_sudo:
docker_cmd = ['sudo'] + docker_cmd
print(f'Running Playwright in Docker using {image}')
if docker_cmd[0] == 'sudo':
print('Note: you may be prompted for your password to run Docker with sudo.')
sys.exit(subprocess.call(docker_cmd))
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'--no-docker',
action='store_true',
help='Run tests on the host machine instead of in Docker')
parser.add_argument(
'--interactive',
'-i',
action='store_true',
help='Run in interactive mode (requires --no-docker)')
parser.add_argument(
'--rebaseline', '-r', action='store_true', help='Rebaseline screenshots')
parser.add_argument('--out', help='out directory')
parser.add_argument('--no-build', action='store_true')
parser.add_argument('--no-depscheck', action='store_true')
parser.add_argument('--workers', help='Number of playwright workers to use')
parser.add_argument('filters', nargs='*')
args = parser.parse_args()
if args.interactive and not args.no_docker:
print('--interactive requires --no-docker (Docker has no display)')
print('Try: ./run-integrationtests --no-docker --interactive')
return 1
run_build_result = run_build(args)
if run_build_result != 0:
print('Build failed, not running tests')
return run_build_result
if args.no_docker:
return run_native(args)
if not is_docker_available():
print('Warning: Docker is not installed or not accessible.')
print('Docker is recommended for consistent test results, especially when')
print('rebaselining screenshots.')
print('')
print('To run without Docker, pass --no-docker:')
print(' ./run-integrationtests --no-docker')
print('')
return 1
return run_in_docker(args)
if __name__ == '__main__':
sys.exit(main())