Skip to content

Commit 0098ac0

Browse files
authored
chore: added Docker image publishing (microsoft#1171)
1 parent b1696fc commit 0098ac0

File tree

8 files changed

+343
-9
lines changed

8 files changed

+343
-9
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: "publish canary docker"
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: "10 0 * * *"
7+
8+
jobs:
9+
publish-canary:
10+
name: "Publish canary Docker"
11+
runs-on: ubuntu-20.04
12+
if: github.repository == 'microsoft/playwright-python'
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Set up Python
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: "3.10"
19+
- name: Install dependencies & browsers
20+
run: |
21+
python -m pip install --upgrade pip wheel
22+
pip install -r local-requirements.txt
23+
pip install -e .
24+
- uses: azure/docker-login@v1
25+
with:
26+
login-server: playwright.azurecr.io
27+
username: playwright
28+
password: ${{ secrets.DOCKER_PASSWORD }}
29+
- name: Set up Docker QEMU for arm64 docker builds
30+
uses: docker/setup-qemu-action@v1
31+
with:
32+
platforms: arm64
33+
- name: publish docker canary
34+
run: ./utils/docker/publish_docker.sh canary
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: "publish release - Docker"
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
is_release:
7+
required: false
8+
type: boolean
9+
description: "Is this a release image?"
10+
11+
release:
12+
types: [published]
13+
14+
jobs:
15+
publish-docker-release:
16+
name: "publish to DockerHub"
17+
runs-on: ubuntu-20.04
18+
if: github.repository == 'microsoft/playwright-python'
19+
steps:
20+
- uses: actions/checkout@v2
21+
- name: Set up Python
22+
uses: actions/setup-python@v2
23+
with:
24+
python-version: "3.10"
25+
- uses: azure/docker-login@v1
26+
with:
27+
login-server: playwright.azurecr.io
28+
username: playwright
29+
password: ${{ secrets.DOCKER_PASSWORD }}
30+
- name: Set up Docker QEMU for arm64 docker builds
31+
uses: docker/setup-qemu-action@v1
32+
with:
33+
platforms: arm64
34+
- name: Install dependencies & browsers
35+
run: |
36+
python -m pip install --upgrade pip wheel
37+
pip install -r local-requirements.txt
38+
pip install -e .
39+
- run: ./utils/docker/publish_docker.sh stable
40+
if: (github.event_name != 'workflow_dispatch' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release == 'true')
41+
- run: ./utils/docker/publish_docker.sh canary
42+
if: (github.event_name != 'workflow_dispatch' && github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release != 'true')

.github/workflows/test_docker.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Test Docker
2+
on:
3+
push:
4+
paths:
5+
- '.github/workflows/test_docker.yml'
6+
branches:
7+
- main
8+
pull_request:
9+
paths:
10+
- '.github/workflows/test_docker.yml'
11+
branches:
12+
- main
13+
jobs:
14+
build:
15+
timeout-minutes: 60
16+
runs-on: ubuntu-20.04
17+
steps:
18+
- uses: actions/checkout@v2
19+
- name: Set up Python
20+
uses: actions/setup-python@v2
21+
with:
22+
python-version: "3.10"
23+
- name: Install dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
pip install -r local-requirements.txt
27+
pip install -e .
28+
- name: Build Docker image
29+
run: bash utils/docker/build.sh --amd64 focal playwright-python:localbuild-focal
30+
- name: Test
31+
run: |
32+
CONTAINER_ID="$(docker run --rm -v $(pwd):/root/playwright --name playwright-docker-test --workdir /root/playwright/ -d -t playwright-python:localbuild-focal /bin/bash)"
33+
docker exec "${CONTAINER_ID}" pip install -r local-requirements.txt
34+
docker exec "${CONTAINER_ID}" pip install -e .
35+
docker exec "${CONTAINER_ID}" python setup.py bdist_wheel
36+
docker exec "${CONTAINER_ID}" xvfb-run pytest -vv tests/sync/
37+
docker exec "${CONTAINER_ID}" xvfb-run pytest -vv tests/async/

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ _repo_version.py
1515
coverage.xml
1616
junit/
1717
htmldocs/
18+
utils/docker/dist/

tests/async/test_queryselector.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from playwright.async_api import Error, Page
44

55

6-
async def test_selectors_register_should_work(selectors, browser):
6+
async def test_selectors_register_should_work(selectors, browser, browser_name):
77
tag_selector = """
88
{
99
create(root, target) {
@@ -17,23 +17,40 @@ async def test_selectors_register_should_work(selectors, browser):
1717
}
1818
}"""
1919

20+
selector_name = f"tag_{browser_name}"
21+
selector2_name = f"tag2_{browser_name}"
22+
2023
# Register one engine before creating context.
21-
await selectors.register("tag", tag_selector)
24+
await selectors.register(selector_name, tag_selector)
2225

2326
context = await browser.new_context()
2427
# Register another engine after creating context.
25-
await selectors.register("tag2", tag_selector)
28+
await selectors.register(selector2_name, tag_selector)
2629

2730
page = await context.new_page()
2831
await page.set_content("<div><span></span></div><div></div>")
2932

30-
assert await page.eval_on_selector("tag=DIV", "e => e.nodeName") == "DIV"
31-
assert await page.eval_on_selector("tag=SPAN", "e => e.nodeName") == "SPAN"
32-
assert await page.eval_on_selector_all("tag=DIV", "es => es.length") == 2
33+
assert (
34+
await page.eval_on_selector(f"{selector_name}=DIV", "e => e.nodeName") == "DIV"
35+
)
36+
assert (
37+
await page.eval_on_selector(f"{selector_name}=SPAN", "e => e.nodeName")
38+
== "SPAN"
39+
)
40+
assert (
41+
await page.eval_on_selector_all(f"{selector_name}=DIV", "es => es.length") == 2
42+
)
3343

34-
assert await page.eval_on_selector("tag2=DIV", "e => e.nodeName") == "DIV"
35-
assert await page.eval_on_selector("tag2=SPAN", "e => e.nodeName") == "SPAN"
36-
assert await page.eval_on_selector_all("tag2=DIV", "es => es.length") == 2
44+
assert (
45+
await page.eval_on_selector(f"{selector2_name}=DIV", "e => e.nodeName") == "DIV"
46+
)
47+
assert (
48+
await page.eval_on_selector(f"{selector2_name}=SPAN", "e => e.nodeName")
49+
== "SPAN"
50+
)
51+
assert (
52+
await page.eval_on_selector_all(f"{selector2_name}=DIV", "es => es.length") == 2
53+
)
3754

3855
# Selector names are case-sensitive.
3956
with pytest.raises(Error) as exc:

utils/docker/Dockerfile.focal

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
FROM ubuntu:focal
2+
3+
ARG DEBIAN_FRONTEND=noninteractive
4+
ARG TZ=America/Los_Angeles
5+
6+
# === INSTALL Python ===
7+
8+
RUN apt-get update && \
9+
# Install Python
10+
apt-get install -y python3 python3-distutils curl && \
11+
update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \
12+
curl -sSL https://bootstrap.pypa.io/get-pip.py -o get-pip.py && \
13+
python get-pip.py && \
14+
rm get-pip.py && \
15+
# Feature-parity with node.js base images.
16+
apt-get install -y --no-install-recommends git openssh-client && \
17+
# clean apt cache
18+
rm -rf /var/lib/apt/lists/* && \
19+
# Create the pwuser
20+
adduser pwuser
21+
22+
# === BAKE BROWSERS INTO IMAGE ===
23+
24+
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
25+
26+
# 1. Add tip-of-tree Playwright package to install its browsers.
27+
# The package should be built beforehand from tip-of-tree Playwright.
28+
COPY ./dist/*-manylinux*.whl /tmp/
29+
30+
# 2. Bake in browsers & deps.
31+
# Browsers will be downloaded in `/ms-playwright`.
32+
# Note: make sure to set 777 to the registry so that any user can access
33+
# registry.
34+
RUN mkdir /ms-playwright && \
35+
mkdir /ms-playwright-agent && \
36+
cd /ms-playwright-agent && \
37+
# if its amd64 then install the manylinux1_x86_64 pip package
38+
if [ "$(uname -m)" = "x86_64" ]; then pip install /tmp/*manylinux1_x86_64*.whl; fi && \
39+
# if its arm64 then install the manylinux1_aarch64 pip package
40+
if [ "$(uname -m)" = "aarch64" ]; then pip install /tmp/*manylinux_2_17_aarch64*.whl; fi && \
41+
playwright install --with-deps && rm -rf /var/lib/apt/lists/* && \
42+
rm /tmp/*.whl && \
43+
rm -rf /ms-playwright-agent && \
44+
chmod -R 777 /ms-playwright

utils/docker/build.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
set -e
3+
set +x
4+
5+
if [[ ($1 == '--help') || ($1 == '-h') || ($1 == '') || ($2 == '') ]]; then
6+
echo "usage: $(basename $0) {--arm64,--amd64} {bionic,focal} playwright:localbuild-focal"
7+
echo
8+
echo "Build Playwright docker image and tag it as 'playwright:localbuild-focal'."
9+
echo "Once image is built, you can run it with"
10+
echo ""
11+
echo " docker run --rm -it playwright:localbuild-focal /bin/bash"
12+
echo ""
13+
echo "NOTE: this requires on Playwright PIP dependencies to be installed"
14+
echo ""
15+
exit 0
16+
fi
17+
18+
function cleanup() {
19+
rm -rf "dist/"
20+
}
21+
22+
trap "cleanup; cd $(pwd -P)" EXIT
23+
cd "$(dirname "$0")"
24+
25+
pushd ../../
26+
python setup.py bdist_wheel --all
27+
popd
28+
mkdir dist/
29+
cp ../../dist/*-manylinux*.whl dist/
30+
31+
PLATFORM=""
32+
if [[ "$1" == "--arm64" ]]; then
33+
PLATFORM="linux/arm64";
34+
elif [[ "$1" == "--amd64" ]]; then
35+
PLATFORM="linux/amd64"
36+
else
37+
echo "ERROR: unknown platform specifier - $1. Only --arm64 or --amd64 is supported"
38+
exit 1
39+
fi
40+
41+
docker build --platform "${PLATFORM}" -t "$3" -f "Dockerfile.$2" .

utils/docker/publish_docker.sh

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/bin/bash
2+
3+
set -e
4+
set +x
5+
6+
trap "cd $(pwd -P)" EXIT
7+
cd "$(dirname "$0")"
8+
9+
MCR_IMAGE_NAME="playwright-python"
10+
PW_VERSION=$(python -c "from playwright._repo_version import version;print(version)")
11+
12+
RELEASE_CHANNEL="$1"
13+
if [[ "${RELEASE_CHANNEL}" == "stable" ]]; then
14+
if [[ "${PW_VERSION}" == *post* ]]; then
15+
echo "ERROR: cannot publish stable docker with Playwright version '${PW_VERSION}'"
16+
exit 1
17+
fi
18+
elif [[ "${RELEASE_CHANNEL}" == "canary" ]]; then
19+
if [[ "${PW_VERSION}" != *post* ]]; then
20+
echo "ERROR: cannot publish canary docker with Playwright version '${PW_VERSION}'"
21+
exit 1
22+
fi
23+
else
24+
echo "ERROR: unknown release channel - ${RELEASE_CHANNEL}"
25+
echo "Must be either 'stable' or 'canary'"
26+
exit 1
27+
fi
28+
29+
if [[ -z "${GITHUB_SHA}" ]]; then
30+
echo "ERROR: GITHUB_SHA env variable must be specified"
31+
exit 1
32+
fi
33+
34+
BIONIC_TAGS=(
35+
"next-bionic"
36+
"v${PW_VERSION}-bionic"
37+
)
38+
if [[ "$RELEASE_CHANNEL" == "stable" ]]; then
39+
BIONIC_TAGS+=("bionic")
40+
fi
41+
42+
FOCAL_TAGS=(
43+
"next"
44+
"sha-${GITHUB_SHA}"
45+
"next-focal"
46+
"v${PW_VERSION}-focal"
47+
"v${PW_VERSION}"
48+
)
49+
50+
if [[ "$RELEASE_CHANNEL" == "stable" ]]; then
51+
FOCAL_TAGS+=("latest")
52+
FOCAL_TAGS+=("focal")
53+
fi
54+
55+
tag_and_push() {
56+
local source="$1"
57+
local target="$2"
58+
echo "-- tagging: $target"
59+
docker tag $source $target
60+
docker push $target
61+
}
62+
63+
publish_docker_images_with_arch_suffix() {
64+
local FLAVOR="$1"
65+
local TAGS=()
66+
if [[ "$FLAVOR" == "bionic" ]]; then
67+
TAGS=("${BIONIC_TAGS[@]}")
68+
elif [[ "$FLAVOR" == "focal" ]]; then
69+
TAGS=("${FOCAL_TAGS[@]}")
70+
else
71+
echo "ERROR: unknown flavor - $FLAVOR. Must be either 'bionic' or 'focal'"
72+
exit 1
73+
fi
74+
local ARCH="$2"
75+
if [[ "$ARCH" != "amd64" && "$ARCH" != "arm64" ]]; then
76+
echo "ERROR: unknown arch - $ARCH. Must be either 'amd64' or 'arm64'"
77+
exit 1
78+
fi
79+
# Prune docker images to avoid platform conflicts
80+
docker system prune -fa
81+
./build.sh "--${ARCH}" "${FLAVOR}" "${MCR_IMAGE_NAME}:localbuild"
82+
83+
for ((i = 0; i < ${#TAGS[@]}; i++)) do
84+
local TAG="${TAGS[$i]}"
85+
tag_and_push "${MCR_IMAGE_NAME}:localbuild" "playwright.azurecr.io/public/${MCR_IMAGE_NAME}:${TAG}-${ARCH}"
86+
done
87+
}
88+
89+
publish_docker_manifest () {
90+
local FLAVOR="$1"
91+
local TAGS=()
92+
if [[ "$FLAVOR" == "bionic" ]]; then
93+
TAGS=("${BIONIC_TAGS[@]}")
94+
elif [[ "$FLAVOR" == "focal" ]]; then
95+
TAGS=("${FOCAL_TAGS[@]}")
96+
else
97+
echo "ERROR: unknown flavor - $FLAVOR. Must be either 'bionic' or 'focal'"
98+
exit 1
99+
fi
100+
101+
for ((i = 0; i < ${#TAGS[@]}; i++)) do
102+
local TAG="${TAGS[$i]}"
103+
local BASE_IMAGE_TAG="playwright.azurecr.io/public/${MCR_IMAGE_NAME}:${TAG}"
104+
local IMAGE_NAMES=""
105+
if [[ "$2" == "arm64" || "$2" == "amd64" ]]; then
106+
IMAGE_NAMES="${IMAGE_NAMES} ${BASE_IMAGE_TAG}-$2"
107+
fi
108+
if [[ "$3" == "arm64" || "$3" == "amd64" ]]; then
109+
IMAGE_NAMES="${IMAGE_NAMES} ${BASE_IMAGE_TAG}-$3"
110+
fi
111+
docker manifest create "${BASE_IMAGE_TAG}" $IMAGE_NAMES
112+
docker manifest push "${BASE_IMAGE_TAG}"
113+
done
114+
}
115+
116+
publish_docker_images_with_arch_suffix focal amd64
117+
publish_docker_images_with_arch_suffix focal arm64
118+
publish_docker_manifest focal amd64 arm64

0 commit comments

Comments
 (0)