diff --git a/.github/workflows/tests_grid.yml b/.github/workflows/tests_grid.yml deleted file mode 100644 index 41ab8a5380bc8..0000000000000 --- a/.github/workflows/tests_grid.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: "tests grid" - -on: - workflow_dispatch: - -env: - FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - -jobs: - test: - name: "Grid" - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - - run: npm ci - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - - run: npm run build - - run: npx playwright install-deps - - run: npx playwright install - - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --retries=0 - env: - PWTEST_MODE: service-grid diff --git a/package-lock.json b/package-lock.json index 1d1f404929e95..2f90fb2a1f70f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1424,10 +1424,6 @@ "resolved": "packages/playwright-ct-vue2", "link": true }, - "node_modules/@playwright/experimental-grid": { - "resolved": "packages/playwright-grid", - "link": true - }, "node_modules/@playwright/test": { "resolved": "packages/playwright-test", "link": true @@ -1528,25 +1524,6 @@ "@types/tern": "*" } }, - "node_modules/@types/commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==", - "deprecated": "This is a stub types definition for commander (https://github.com/tj/commander.js). commander provides its own type definitions, so you don't need @types/commander installed!", - "dev": true, - "dependencies": { - "commander": "*" - } - }, - "node_modules/@types/debug": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", - "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", - "dev": true, - "dependencies": { - "@types/ms": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", @@ -1567,12 +1544,6 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, - "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, "node_modules/@types/node": { "version": "16.18.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.34.tgz", @@ -6199,6 +6170,7 @@ }, "node_modules/ws": { "version": "8.5.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -6564,6 +6536,7 @@ "packages/playwright-grid": { "name": "@playwright/experimental-grid", "version": "0.0.1", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "commander": "^11.0.0", @@ -6583,25 +6556,6 @@ "node": ">=16" } }, - "packages/playwright-grid/node_modules/commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", - "engines": { - "node": ">=16" - } - }, - "packages/playwright-grid/node_modules/playwright-core": { - "version": "1.38.0-alpha-aug-10-2023", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0-alpha-aug-10-2023.tgz", - "integrity": "sha512-j3+Lmd9ySH2EYv45AgcDccr5v5uBVljnmCV+QYAcrhQ4hGCAoUwGTXEuvPnEgw1BCqF7QfmugeVzEi1UD8PnIQ==", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, "packages/playwright-test": { "name": "@playwright/test", "version": "1.38.0-next", @@ -7574,30 +7528,6 @@ "vue": "^2.7.14" } }, - "@playwright/experimental-grid": { - "version": "file:packages/playwright-grid", - "requires": { - "@types/commander": "^2.12.2", - "@types/debug": "^4.1.8", - "@types/ws": "^8.5.5", - "commander": "^11.0.0", - "debug": "^4.3.2", - "playwright-core": "1.38.0-alpha-aug-10-2023", - "ws": "^8.1.0" - }, - "dependencies": { - "commander": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", - "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==" - }, - "playwright-core": { - "version": "1.38.0-alpha-aug-10-2023", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0-alpha-aug-10-2023.tgz", - "integrity": "sha512-j3+Lmd9ySH2EYv45AgcDccr5v5uBVljnmCV+QYAcrhQ4hGCAoUwGTXEuvPnEgw1BCqF7QfmugeVzEi1UD8PnIQ==" - } - } - }, "@playwright/test": { "version": "file:packages/playwright-test", "requires": { @@ -7686,24 +7616,6 @@ "@types/tern": "*" } }, - "@types/commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==", - "dev": true, - "requires": { - "commander": "*" - } - }, - "@types/debug": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", - "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", - "dev": true, - "requires": { - "@types/ms": "*" - } - }, "@types/estree": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", @@ -7723,12 +7635,6 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, - "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, "@types/node": { "version": "16.18.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.34.tgz", @@ -10755,6 +10661,7 @@ }, "ws": { "version": "8.5.0", + "dev": true, "requires": {} }, "xml2js": { diff --git a/packages/playwright-grid/.npmignore b/packages/playwright-grid/.npmignore deleted file mode 100644 index 2c21d1b6f922c..0000000000000 --- a/packages/playwright-grid/.npmignore +++ /dev/null @@ -1,5 +0,0 @@ -**/* -!cli.js -!lib/**/*.js -!README.md -!https/* diff --git a/packages/playwright-grid/Dockerfile b/packages/playwright-grid/Dockerfile deleted file mode 100644 index b8992e29745f6..0000000000000 --- a/packages/playwright-grid/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM mcr.microsoft.com/playwright:v1.37.0-alpha-aug-7-2023-jammy - -WORKDIR /app - -COPY package.json ./ -COPY cli.js ./ -COPY lib ./lib -RUN npm install diff --git a/packages/playwright-grid/README.md b/packages/playwright-grid/README.md deleted file mode 100644 index 0fb4f14198452..0000000000000 --- a/packages/playwright-grid/README.md +++ /dev/null @@ -1 +0,0 @@ -# wip diff --git a/packages/playwright-grid/cli.js b/packages/playwright-grid/cli.js deleted file mode 100755 index cfa17e91590e6..0000000000000 --- a/packages/playwright-grid/cli.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -require('./lib/cli.js'); diff --git a/packages/playwright-grid/deployment-grid.yaml b/packages/playwright-grid/deployment-grid.yaml deleted file mode 100644 index f355b54ef227e..0000000000000 --- a/packages/playwright-grid/deployment-grid.yaml +++ /dev/null @@ -1,46 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: grid-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: grid - template: - metadata: - labels: - app: grid - spec: - containers: - - name: grid - image: playwright-grid - resources: - requests: - cpu: "2" - memory: "4Gi" - imagePullPolicy: IfNotPresent - env: - - name: DEBUG - value: "pw:grid*" - - name: PLAYWRIGHT_GRID_ACCESS_KEY - valueFrom: - secretKeyRef: - name: access-key-secret - key: access-key - command: ["node", "./cli.js"] - args: ["grid", "--port=3000"] - ---- -apiVersion: v1 -kind: Service -metadata: - name: grid-service -spec: - selector: - app: grid - ports: - - protocol: TCP - port: 3000 - targetPort: 3000 - type: LoadBalancer diff --git a/packages/playwright-grid/deployment-worker.yaml b/packages/playwright-grid/deployment-worker.yaml deleted file mode 100644 index f7cfc57cdd86f..0000000000000 --- a/packages/playwright-grid/deployment-worker.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: worker-deployment -spec: - replicas: 10 # or however many nodes you want - selector: - matchLabels: - app: worker - template: - metadata: - labels: - app: worker - spec: - containers: - - name: grid - image: playwright-grid - resources: - requests: - cpu: "2" - memory: "4Gi" - imagePullPolicy: IfNotPresent - env: - - name: DEBUG - value: "pw:grid*" - - name: PLAYWRIGHT_GRID_ACCESS_KEY - valueFrom: - secretKeyRef: - name: access-key-secret - key: access-key - command: ["node", "./cli.js"] - args: ["node", "--grid=grid-service:3000"] diff --git a/packages/playwright-grid/docs/azure.md b/packages/playwright-grid/docs/azure.md deleted file mode 100644 index bdf781532c511..0000000000000 --- a/packages/playwright-grid/docs/azure.md +++ /dev/null @@ -1,57 +0,0 @@ -```sh -# Create resource group -az group create --name group-grid-001 --location westus3 - -# Create ACR -az acr create --resource-group group-grid-001 --name acrgrid001 --sku Basic -az acr login --name acrgrid001 -az acr list --resource-group group-grid-001 --query "[].{acrLoginServer:loginServer}" --output table - -# Create AKS -az aks create --resource-group group-grid-001 --name aks-grid-001 --node-count 4 --enable-addons monitoring --generate-ssh-keys -az aks get-credentials --resource-group group-grid-001 --name aks-grid-001 - -# Grant AKS access to ACR -az aks show --resource-group group-grid-001 --name aks-grid-001 --query "servicePrincipalProfile.clientId" --output tsv -# az aks show --resource-group group-grid-001 --name aks-grid-001 --query "identityProfile.kubeletidentity.clientId" -o tsv -# az acr show --name acrgrid001 --resource-group group-grid-001 --query "id" -o tsv -# az role assignment create --assignee --role AcrPull --scope - -# Create secrets -kubectl create secret generic access-key-secret --from-literal=access-key=$PLAYWRIGHT_GRID_ACCESS_KEY - -# Create TLS -# kubectl create secret tls grid-tls-secret --cert=../../tests/config/testserver/cert.pem --key=../../tests/config/testserver/key.pem -# az network public-ip create --resource-group MC_group-grid-001_aks-grid-001_westus3 --name public-ip-grid-001 --sku Standard --allocation-method static -# az network public-ip show --resource-group MC_group-grid-001_aks-grid-001_westus3 --name public-ip-grid-001 --query ipAddress --output tsv -# # use output below -# helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx -# helm install nginx-ingress ingress-nginx/ingress-nginx \ -# --set controller.replicaCount=1 \ -# --set controller.nodeSelector."beta\.kubernetes\.io/os"=linux \ -# --set defaultBackend.nodeSelector."beta\.kubernetes\.io/os"=linux \ -# --set controller.service.loadBalancerIP="20.118.130.255" - -# Push Docker container -docker build -t playwright-grid:latest -f Dockerfile . -docker tag playwright-grid acrgrid001.azurecr.io/playwright-grid -docker push acrgrid001.azurecr.io/playwright-grid - -# Delete deployment -kubectl delete deployment grid-deployment -kubectl delete deployment worker-deployment -kubectl delete svc grid-service - -# Update deployment -kubectl apply -f deployment-grid.yaml -kubectl apply -f deployment-worker.yaml - -# Debug -kubectl config -kubectl get pods -l app=grid -kubectl logs grid-6cbbfc866c-wh8dw -kubectl get pods -n ingress-basic -kubectl get svc grid-service -kubectl describe node -az aks show --resource-group group-grid-001 --name aks-grid-001 --query fqdn --output tsv -``` diff --git a/packages/playwright-grid/docs/minikube.md b/packages/playwright-grid/docs/minikube.md deleted file mode 100644 index d60836831899c..0000000000000 --- a/packages/playwright-grid/docs/minikube.md +++ /dev/null @@ -1,31 +0,0 @@ -```sh -minikube config set memory 65536 -minikube config set cpus 12 -minikube start -minikube dashboard - -# Point docker to minikube -minikube -p minikube docker-env -eval $(minikube docker-env) -kubectl config use-context minikube -kubectl create secret generic access-key-secret --from-literal=access-key=$PLAYWRIGHT_GRID_ACCESS_KEY - -# Push Docker container -docker build -t playwright-grid:latest -f Dockerfile . - -# Delete deployment -kubectl delete deployment grid-deployment -kubectl delete deployment worker-deployment -kubectl delete svc grid-service - -# Update deployment - -kubectl apply -f deployment-grid.yaml -kubectl apply -f deployment-worker.yaml - -# Debug -minikube ip -kubectl get svc grid-service -kubectl get pods -l app=grid -kubectl logs grid-6cbbfc866c-wh8dw -``` \ No newline at end of file diff --git a/packages/playwright-grid/https/cert.pem b/packages/playwright-grid/https/cert.pem deleted file mode 100644 index 3388ed5e982d9..0000000000000 --- a/packages/playwright-grid/https/cert.pem +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFCjCCAvKgAwIBAgIULU/gkDm8IqC7PG8u3RID0AYyP6gwDQYJKoZIhvcNAQEL -BQAwGjEYMBYGA1UEAwwPcGxheXdyaWdodC10ZXN0MB4XDTIzMDgxMDIyNTc1MFoX -DTMzMDgwNzIyNTc1MFowGjEYMBYGA1UEAwwPcGxheXdyaWdodC10ZXN0MIICIjAN -BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArbS99qjKcnHr5G0Zc2xhDaOZnjQv -Fbiqxf/nbXt/7WaqryzpVKu7AT1ainBvuPEo7If9DhVnfF//2pGl0gbU31OU4/mr -ymQmczGEyZvOBDsZhtCif54o5OoO0BjhODNT8OWec9RT87n6RkH58MHlOi8xsPxQ -9n5U1CN/h2DyQF3aRKunEFCgtwPKWSjG+J/TAI9i0aSENXPiR8wjTrjg79s8Ehuj -NN8Wk6rKLU3sepG3GIMID5vLsVa2t9xqn562sP95Ee+Xp2YX3z7oYK99QCJdzacw -alhMHob1GCEKjDyxsD2IFRi7Dysiutfyzy3pMo6NALxFrwKVhWX0L4zVFIsI6JlV -dK8dHmDk0MRSqgB9sWXvEfSTXADEe8rncFSFpFz4Z8RNLmn5YSzQJzokNn41DUCP -dZTlTkcGTqvn5NqoY4sOV8rkFbgmTcqyijV/sebPjxCbJNcNmaSWa9FJ5IjRTpzM -38wLmxn+eKGK68n2JB3P7JP6LtsBShQEpXAF3rFfyNsP1bjquvGZVSjV8w/UwPE4 -kV5eq3j3D4913Zfxvzjp6PEmhStG0EQtIXvx/TRoYpaNWypIgZdbkZQp1HUIQL15 -D2Web4nazP3so1FC3ZgbrJZ2ozoadjLMp49NcSFdh+WRyVKuo0DIqR0zaiAzzf2D -G1q7TLKimM3XBMUCAwEAAaNIMEYwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwLAYD -VR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqG -SIb3DQEBCwUAA4ICAQAvC5M1JFc21WVSLPvE2iVbt4HmirO3EENdDqs+rTYG5VJG -iE5ZuI6h/LjS5ptTfKovXQKaMr3pwp1pLMd/9q+6ZR1Hs9Z2wF6OZan4sb0uT32Y -1KGlj86QMiiSLdrJ/1Z9JHskHYNCep1ZTsUhGk0qqiNv+G3K2y7ZpvrT/xlnYMth -KLTuSVUwM8BBEPrCRLoXuaEy0LnvMvMVepIfP8tnMIL6zqmj3hXMPe4r4OFV/C5o -XX25bC7GyuPWIRYn2OWP92J1CODZD1rGRoDtmvqrQpHdeX9RYcKH0ZLZoIf5L3Hf -pPUtVkw3QGtjvKeG3b9usxaV9Od2Z08vKKk1PRkXFe8gqaeyicK7YVIOMTSuspAf -JeJEHns6Hg61Exbo7GwdX76xlmQ/Z43E9BPHKgLyZ9WuJ0cysqN4aCyvS9yws9to -ki7iMZqJUsmE2o09n9VaEsX6uQANZtLjI9wf+IgJuueDTNrkzQkhU7pbaPMsSG40 -AgGY/y4BR0H8sbhNnhqtZH7RcXV9VCJoPBAe+YiuXRiXyZHWxwBRyBE3e7g4MKHg -hrWtaWUAs7gbavHwjqgU63iVItDSk7t4fCiEyObjK09AaNf2DjjaSGf8YGza4bNy -BjYinYJ6/eX//gp+abqfocFbBP7D9zRDgMIbVmX/Ey6TghKiLkZOdbzcpO4Wgg== ------END CERTIFICATE----- diff --git a/packages/playwright-grid/https/key.pem b/packages/playwright-grid/https/key.pem deleted file mode 100644 index 28edf511d84d0..0000000000000 --- a/packages/playwright-grid/https/key.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -ZkZVSOEp+sYBf/tmptlKr49nO+dTjQ== ------END PRIVATE KEY----- diff --git a/packages/playwright-grid/https/san.cnf b/packages/playwright-grid/https/san.cnf deleted file mode 100644 index 2f4864b85fe95..0000000000000 --- a/packages/playwright-grid/https/san.cnf +++ /dev/null @@ -1,19 +0,0 @@ -# openssl req -new -x509 -days 3650 -key key.pem -out cert.pem -config san.cnf -extensions v3_req - -[req] -distinguished_name = req_distinguished_name -req_extensions = v3_req -prompt = no - -[req_distinguished_name] -CN = playwright-test - -[v3_req] -basicConstraints = CA:FALSE -keyUsage = nonRepudiation, digitalSignature, keyEncipherment -subjectAltName = @alt_names - -[alt_names] -DNS.1 = localhost -IP.1 = 127.0.0.1 -IP.2 = ::1 diff --git a/packages/playwright-grid/package.json b/packages/playwright-grid/package.json deleted file mode 100644 index 2cc1bee868e21..0000000000000 --- a/packages/playwright-grid/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@playwright/experimental-grid", - "version": "0.0.1", - "private": true, - "description": "Playwright Grid", - "scripts": {}, - "bin": { - "playwright-grid": "./cli.js" - }, - "dependencies": { - "commander": "^11.0.0", - "debug": "^4.3.2", - "playwright-core": "1.38.0-alpha-aug-10-2023", - "ws": "^8.1.0" - }, - "devDependencies": { - "@types/commander": "^2.12.2", - "@types/debug": "^4.1.8", - "@types/ws": "^8.5.5" - }, - "repository": "github:Microsoft/playwright", - "engines": { - "node": ">=16" - }, - "homepage": "https://playwright.dev", - "author": { - "name": "Microsoft Corporation" - }, - "license": "Apache-2.0" -} diff --git a/packages/playwright-grid/src/cli.ts b/packages/playwright-grid/src/cli.ts deleted file mode 100644 index c405bb8e16ceb..0000000000000 --- a/packages/playwright-grid/src/cli.ts +++ /dev/null @@ -1,52 +0,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. - */ - -import { program } from 'commander'; -const packageJSON = require('../package.json'); - -program - .version('Version ' + packageJSON.version) - .name('playwright-grid'); - -program - .command('grid') - .option('--port ', 'port to listen to, 3333 by default') - .option('--access-key ', 'access key to the grid') - .option('--https-cert ', 'path to the HTTPS certificate') - .option('--https-key ', 'path to the HTTPS key') - .action(async opts => { - const port = opts.port || +(process.env.PLAYWRIGHT_GRID_PORT || '3333'); - const accessKey = opts.accessKey || process.env.PLAYWRIGHT_GRID_ACCESS_KEY; - const httpsCert = opts.httpsCert || process.env.PLAYWRIGHT_GRID_HTTPS_CERT; - const httpsKey = opts.httpsKey || process.env.PLAYWRIGHT_GRID_HTTPS_KEY; - const { Grid } = await import('./grid/grid.js'); - const grid = await Grid.create({ port, accessKey, httpsCert, httpsKey }); - grid.start(); - }); - -program - .command('node') - .option('--grid ', 'grid address', 'localhost:3333') - .option('--capacity ', 'node capacity', '1') - .option('--access-key ', 'access key to the grid', '') - .action(async opts => { - const { Node } = await import('./node/node.js'); - const accessKey = opts.accessKey || process.env.PLAYWRIGHT_GRID_ACCESS_KEY; - const node = new Node(opts.grid, +opts.capacity, accessKey); - await node.connect(); - }); - -program.parse(process.argv); diff --git a/packages/playwright-grid/src/common/capabilities.ts b/packages/playwright-grid/src/common/capabilities.ts deleted file mode 100644 index 2a3aaa6390a89..0000000000000 --- a/packages/playwright-grid/src/common/capabilities.ts +++ /dev/null @@ -1,19 +0,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. - */ - -export type Capabilities = { - platform?: typeof process.platform; -}; diff --git a/packages/playwright-grid/src/common/httpServer.ts b/packages/playwright-grid/src/common/httpServer.ts deleted file mode 100644 index 581a83c7cf09c..0000000000000 --- a/packages/playwright-grid/src/common/httpServer.ts +++ /dev/null @@ -1,132 +0,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. - */ - -import debug from 'debug'; -import fs from 'fs'; -import https from 'https'; -import http from 'http'; -import path from 'path'; -import { URL } from 'url'; -import { Server as WebSocketServer } from 'ws'; - -export type ServerRouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => boolean; - -export class HttpServer { - private _log: debug.Debugger; - readonly server: https.Server | http.Server; - private _urlPrefix: string; - private _routes: { prefix?: string, exact?: string, handler: ServerRouteHandler }[] = []; - private _isSecure: boolean; - - static async create(options: { httpsKey?: string, httpsCert?: string }) { - if (options.httpsKey && options.httpsCert) { - return new HttpServer({ - key: await fs.promises.readFile(options.httpsKey, 'utf8'), - cert: await fs.promises.readFile(options.httpsCert, 'utf8'), - }); - } - return new HttpServer(); - } - - private constructor(options?: { key: string, cert: string }) { - this._log = debug(`pw:grid:http`); - this._urlPrefix = ''; - this._isSecure = !!options; - this.server = options ? https.createServer(options, this._onRequest.bind(this)) : http.createServer(this._onRequest.bind(this)); - } - - routePrefix(prefix: string, handler: ServerRouteHandler) { - this._routes.push({ prefix, handler }); - } - - routePath(path: string, handler: ServerRouteHandler) { - this._routes.push({ exact: path, handler }); - } - - createWebSocketServer() { - return new WebSocketServer({ server: this.server }); - } - - async start(port?: number): Promise { - this._log('starting server', port); - this.server.listen(port); - await new Promise(cb => this.server!.once('listening', cb)); - const address = this.server.address(); - this._urlPrefix = typeof address === 'string' ? address : `${this._isSecure ? 'https' : 'http'}://127.0.0.1:${address!.port}`; - return this._urlPrefix; - } - - async stop() { - await new Promise(cb => this.server!.close(cb)); - } - - urlPrefix() { - return this._urlPrefix; - } - - serveFile(response: http.ServerResponse, absoluteFilePath: string, headers?: { [name: string]: string }): boolean { - try { - const content = fs.readFileSync(absoluteFilePath); - response.statusCode = 200; - const contentType = extensionToMime[path.extname(absoluteFilePath).substring(1)] || 'application/octet-stream'; - response.setHeader('Content-Type', contentType); - response.setHeader('Content-Length', content.byteLength); - for (const [name, value] of Object.entries(headers || {})) - response.setHeader(name, value); - response.end(content); - return true; - } catch (e) { - return false; - } - } - - private _onRequest(request: http.IncomingMessage, response: http.ServerResponse) { - this._log('web request', request.url); - request.on('error', () => response.end()); - try { - if (!request.url) { - response.end(); - return; - } - const url = new URL('http://localhost' + request.url); - this._log('url pathname', url.pathname); - for (const route of this._routes) { - if (route.exact && url.pathname === route.exact && route.handler(request, response)) - return; - if (route.prefix && url.pathname.startsWith(route.prefix) && route.handler(request, response)) - return; - } - response.statusCode = 404; - response.end(); - } catch (e) { - response.end(); - } - } -} - -const extensionToMime: { [key: string]: string } = { - 'css': 'text/css', - 'html': 'text/html', - 'jpeg': 'image/jpeg', - 'jpg': 'image/jpeg', - 'js': 'application/javascript', - 'png': 'image/png', - 'ttf': 'font/ttf', - 'svg': 'image/svg+xml', - 'webp': 'image/webp', - 'woff': 'font/woff', - 'woff2': 'font/woff2', -}; diff --git a/packages/playwright-grid/src/grid/grid.ts b/packages/playwright-grid/src/grid/grid.ts deleted file mode 100644 index 9e76f07c40893..0000000000000 --- a/packages/playwright-grid/src/grid/grid.ts +++ /dev/null @@ -1,376 +0,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. - */ - -import crypto from 'crypto'; -import debug from 'debug'; -import { URL } from 'url'; -import WebSocket from 'ws'; -import type { Server as WebSocketServer } from 'ws'; -import { HttpServer } from '../common/httpServer'; -import type { Capabilities } from '../common/capabilities'; -import type http from 'http'; -import type stream from 'stream'; - -class WebSocketRequest { - private _socketError: Error | undefined; - - constructor(readonly wsServer: WebSocketServer, readonly request: http.IncomingMessage, readonly socket: stream.Duplex, readonly head: Buffer) { - this.socket.on('error', e => this._socketError = e); - } - - upgrade(extraHeaders: string[] = []): Promise { - if (this._socketError || this.socket.destroyed) - return Promise.resolve(null); - - return new Promise(f => { - const socketEndTimer = setTimeout(() => { - this.socket.destroy(); - f(null); - }, 5000); - this.wsServer.once('headers', headers => { - for (let i = 0; i < extraHeaders.length; i += 2) { - if (extraHeaders[i].toLowerCase().startsWith('x-playwright')) - headers.push(`${extraHeaders[i]}: ${extraHeaders[i + 1]}`); - } - }); - this.wsServer.handleUpgrade(this.request, this.socket, this.head, ws => { - if (ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) { - f(null); - return; - } - clearTimeout(socketEndTimer); - this.wsServer.emit('connection', ws, this.request); - f(ws); - }); - }); - } -} - -type ClientRequest = { - webSocketRequest: WebSocketRequest; - capabilities: Capabilities; -}; - -class Worker { - readonly workerId = 'worker@' + createGuid(); - private _workerSocketRequest: WebSocketRequest | undefined; - private _workerSocket: WebSocket | undefined; - private _clientSocket: WebSocket | undefined; - private _log: debug.Debugger; - private _state: 'new' | 'available' | 'connecting' | 'connected' | 'closed' = 'new'; - private _onClose: () => void; - private _retireTimer: NodeJS.Timeout; - - constructor(onClose: () => void) { - this._log = debug(`pw:grid:${this.workerId}`); - this._onClose = onClose; - this._log('worker created'); - - // Workers have 30 seconds to be picked up. - this._retireTimer = setTimeout(() => { - this.close(); - }, 30_000); - } - - state(): 'new' | 'available' | 'connecting' | 'connected' | 'closed' { - return this._state; - } - - workerConnected(workerSocketRequest: WebSocketRequest) { - this._log('worker available'); - this._state = 'available'; - this._workerSocketRequest = workerSocketRequest; - } - - async connect(clientRequest: ClientRequest): Promise<'workerError' | 'clientError' | 'success'> { - this._log('connect', clientRequest.webSocketRequest.request.headers); - this._state = 'connecting'; - - clearTimeout(this._retireTimer); - - const workerSocket = await this._workerSocketRequest!.upgrade(clientRequest.webSocketRequest.request.rawHeaders); - if (!workerSocket || workerSocket.readyState === WebSocket.CLOSED || workerSocket.readyState === WebSocket.CLOSING) { - this.close(); - return 'workerError'; - } - - const clientSocket = await clientRequest.webSocketRequest.upgrade(); - if (!clientSocket || clientSocket.readyState === WebSocket.CLOSED || clientSocket.readyState === WebSocket.CLOSING) { - this.close(); - return 'clientError'; - } - - this._wire(workerSocket, clientSocket); - return 'success'; - } - - private _wire(workerSocket: WebSocket, clientSocket: WebSocket) { - this._log('connected'); - - this._state = 'connected'; - workerSocket.on('close', () => this.close()); - workerSocket.on('error', () => this.close()); - clientSocket.on('close', () => this.close()); - clientSocket.on('error', () => this.close()); - clientSocket.on('message', data => { - this._workerSocket?.send(data); - }); - workerSocket.on('message', data => { - this._clientSocket?.send(data); - }); - - this._workerSocket = workerSocket; - this._clientSocket = clientSocket; - } - - close() { - if (this._state === 'closed') - return; - this._log('close'); - this._state = 'closed'; - this._workerSocket?.close(); - this._clientSocket?.close(); - this._workerSocket = undefined; - this._clientSocket = undefined; - this._onClose(); - } - - debugInfo() { - return { state: this._state }; - } -} - -class Node { - readonly nodeId = 'node@' + createGuid(); - private _ws: WebSocket; - readonly _workers = new Map(); - private _log: debug.Debugger; - private _onWorkersChanged: () => void; - private _onClose: () => void; - private _capabilities: Capabilities; - private _capacity: number; - - constructor(ws: WebSocket, capacity: number, capabilities: Capabilities, onWorkersChanged: () => void, onClose: () => void) { - this._capabilities = capabilities; - this._capacity = capacity; - this._log = debug(`pw:grid:${this.nodeId}`); - ws.on('close', () => this.close()); - ws.on('error', () => this.close()); - ws.send(JSON.stringify({ nodeId: this.nodeId })); - this._ws = ws; - this._onWorkersChanged = onWorkersChanged; - this._onClose = onClose; - } - - hasWorker(workerId: string) { - return this._workers.has(workerId); - } - - hasCapabilities(capabilities: Capabilities): boolean { - return !capabilities.platform || this._capabilities.platform === capabilities.platform; - } - - workers() { - return [...this._workers.values()]; - } - - canCreateWorker() { - return this._workers.size < this._capacity; - } - - createWorker() { - const worker = new Worker(() => { - this._workers.delete(worker.workerId); - this._onWorkersChanged(); - }); - this._workers.set(worker.workerId, worker); - this._ws.send(JSON.stringify({ workerId: worker.workerId })); - return worker; - } - - workerConnected(workerId: string, webSocketRequest: WebSocketRequest) { - const worker = this._workers.get(workerId); - if (worker) { - worker.workerConnected(webSocketRequest); - this._onWorkersChanged(); - } - } - - close() { - this._log('close'); - this._ws?.close(); - this._onClose(); - } -} - -export class Grid { - private _server: HttpServer; - private _wsServer: WebSocketServer; - private _nodes = new Map(); - private _log: debug.Debugger; - private _clientRequests: ClientRequest[] = []; - private _port: number; - private _accessKey: string; - - static async create(options: { port: number, accessKey?: string, httpsCert?: string, httpsKey?: string }): Promise { - const server = await HttpServer.create(options); - return new Grid(server, options); - } - - private constructor(server: HttpServer, options: { port: number, accessKey?: string }) { - this._log = debug(`pw:grid:proxy`); - this._server = server; - this._port = options.port; - this._accessKey = options.accessKey || ''; - - this._server.routePath('/' + this._accessKey, (request, response) => { - response.statusCode = 200; - response.setHeader('Content-Type', 'text/plain'); - response.end(this._state()); - return true; - }); - - this._wsServer = new WebSocket.Server({ noServer: true }); - this._wsServer.on('connection', ws => { - ws.on('error', e => this._log(e)); - }); - this._server.server.on('upgrade', async (request, socket, head) => { - this._log('upgrade', request.url, request.headers); - - if (this._accessKey && request.headers['x-playwright-access-key'] !== this._accessKey) { - socket.destroy(); - return; - } - - const url = new URL('http://internal' + request.url); - const params = url.searchParams; - - if (url.pathname.startsWith('/registerNode')) { - const nodeRequest = new WebSocketRequest(this._wsServer, request, socket, head); - const ws = await nodeRequest.upgrade(); - if (!ws) - return; - const capacity = +(params.get('capacity') || '1'); - const capabilities = JSON.parse(params.get('caps')!) as Capabilities; - const node = new Node(ws, capacity, capabilities, () => { - this._makeAMatch(); - }, () => { - this._nodes.delete(node.nodeId); - }); - this._nodes.set(node.nodeId, node); - this._log('register node', node.nodeId); - return; - } - - if (url.pathname.startsWith('/registerWorker')) { - const nodeId = params.get('nodeId')!; - const workerId = params.get('workerId')!; - const node = this._nodes.get(nodeId); - if (!node) { - socket.destroy(); - return; - } - if (!node.hasWorker(workerId)) { - socket.destroy(); - return; - } - const workerRequest = new WebSocketRequest(this._wsServer, request, socket, head); - node.workerConnected(workerId, workerRequest); - return; - } - - if (url.pathname === '/') { - const capabilities = JSON.parse(params.get('caps') || '{}') as Capabilities; - this._addClientRequest({ - webSocketRequest: new WebSocketRequest(this._wsServer, request, socket, head), - capabilities, - }); - return; - } - }); - } - - private _addClientRequest(clientRequest: ClientRequest) { - this._clientRequests.push(clientRequest); - this._makeAMatch(); - } - - private _nodesWithCapabilities(capabilities: Capabilities | null): Node[] { - return [...this._nodes.values()].filter(node => !capabilities || node.hasCapabilities(capabilities)); - } - - private _workers(capabilities: Capabilities | null): Worker[] { - const result: Worker[] = []; - for (const node of this._nodesWithCapabilities(capabilities)) - result.push(...node.workers()); - return result; - } - - private _makeAMatch() { - this._log('making a match', { - clients: this._clientRequests.length, - nodes: this._nodes.size, - workers: this._workers(null).length, - availableWorkers: this._workers(null).filter(w => w.state() === 'available').length - }); - - // Remove closed client requests. - this._clientRequests = this._clientRequests.filter(c => c.webSocketRequest.socket.readable); - - if (!this._clientRequests.length) - return; - - const capabilities = this._clientRequests[0].capabilities; - const nodes = this._nodesWithCapabilities(capabilities); - const availableWorkers = nodes.map(n => n.workers()).flat().filter(w => w.state() === 'available'); - if (!availableWorkers.length) { - // Try getting another worker for given capabilities. - const node = nodes.find(w => w.canCreateWorker()); - if (node) - node.createWorker(); - return; - } - - // Make a match. - const worker = availableWorkers[0]; - const clientRequest = this._clientRequests.shift()!; - worker.connect(clientRequest).then(result => { - if (result === 'workerError') - this._clientRequests.unshift(clientRequest); - this._makeAMatch(); - }).catch(e => this._log(e)); - } - - private _state(): string { - const lines = [this._nodes.size + ' Nodes(s)']; - for (const [nodeId, node] of this._nodes) { - lines.push(` node ${nodeId}`); - for (const [workerId, worker] of node._workers) - lines.push(` ${workerId} - ${JSON.stringify(worker.debugInfo())}`); - } - return lines.join('\n'); - } - - async start() { - const url = await this._server.start(this._port); - // eslint-disable-next-line no-console - console.log('Server is listening on: ' + url); - } -} - -function createGuid(): string { - return crypto.randomBytes(16).toString('hex'); -} diff --git a/packages/playwright-grid/src/node/node.ts b/packages/playwright-grid/src/node/node.ts deleted file mode 100644 index 0e949ef86f11a..0000000000000 --- a/packages/playwright-grid/src/node/node.ts +++ /dev/null @@ -1,104 +0,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. - */ - -import WebSocket from 'ws'; -import child_process from 'child_process'; -import debug from 'debug'; -import type { Capabilities } from '../common/capabilities'; - -const log = debug('pw:grid:node'); - -const caps: Capabilities = { - platform: process.platform, -}; - -export class Node { - workerSeq = 0; - - constructor(readonly grid: string, readonly capacity: number, readonly accessKey?: string) { - this.accessKey = accessKey || ''; - log('node created', accessKey); - } - - async connect() { - const wsGrid = this.grid; - const url = wsGrid + `/registerNode?capacity=${this.capacity}&caps=${JSON.stringify(caps)}`; - - for (let i = 0; i < 5; ++i) { - const ws = await this._connect(url); - if (ws) { - this._wire(ws, wsGrid); - return; - } - await new Promise(f => setTimeout(f, 5000)); - } - - // eslint-disable-next-line no-restricted-properties - process.exit(0); - } - - private async _connect(url: string): Promise { - return await new Promise(resolve => { - log('connecting', url); - const ws = new WebSocket(url, { - headers: { - 'x-playwright-access-key': this.accessKey, - } - }); - ws.on('error', error => { - log(error); - resolve(null); - }); - ws.on('open', () => { - log('connected', this.grid); - resolve(ws); - }); - }); - } - - private _wire(ws: WebSocket, wsGrid: string) { - ws.on('close', () => { - // eslint-disable-next-line no-restricted-properties - process.exit(0); - }); - ws.on('error', () => { - // eslint-disable-next-line no-restricted-properties - process.exit(0); - }); - let nodeId = ''; - ws.on('message', data => { - const text = data.toString(); - const message = JSON.parse(text); - if (message.nodeId) { - nodeId = message.nodeId; - log('node id', nodeId); - return; - } - const workerId = message.workerId; - log('worked requested', workerId); - child_process.fork(require.resolve('./worker.js'), { - env: { - ...process.env, - PLAYWRIGHT_GRID_NODE_ID: nodeId, - PLAYWRIGHT_GRID_WORKER_ID: workerId, - PLAYWRIGHT_GRID_ENDPOINT: wsGrid, - PLAYWRIGHT_GRID_ACCESS_KEY: this.accessKey, - }, - detached: true - }); - }); - } -} diff --git a/packages/playwright-grid/src/node/worker.ts b/packages/playwright-grid/src/node/worker.ts deleted file mode 100644 index 4dacddd6de1ba..0000000000000 --- a/packages/playwright-grid/src/node/worker.ts +++ /dev/null @@ -1,96 +0,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. - */ - -import debug from 'debug'; -import WebSocket from 'ws'; -import { DispatcherConnection, RootDispatcher, PlaywrightDispatcher, createPlaywright, serverSideCallMetadata, SocksProxy } from 'playwright-core/lib/server'; -import { gracefullyCloseAll } from 'playwright-core/lib/utils'; -import type { Playwright } from 'playwright-core/lib/server'; - -const workerId = process.env.PLAYWRIGHT_GRID_WORKER_ID!; -const log = debug('pw:grid:worker@' + workerId); - -class Worker { - constructor() { - log('worker created'); - const dispatcherConnection = new DispatcherConnection(); - let browserName: 'chromium' | 'webkit' | 'firefox'; - let launchOptions: any; - let proxyPattern: string | undefined; - let socksProxy: SocksProxy | undefined; - - const dispose = async () => { - dispatcherConnection.onmessage = () => {}; - // eslint-disable-next-line no-restricted-properties - setTimeout(() => process.exit(0), 30000); - await Promise.all([ - socksProxy?.close(), - gracefullyCloseAll(), - ]).catch(() => {}); - // eslint-disable-next-line no-restricted-properties - process.exit(0); - }; - - const ws = new WebSocket(process.env.PLAYWRIGHT_GRID_ENDPOINT + `/registerWorker?nodeId=${process.env.PLAYWRIGHT_GRID_NODE_ID}&workerId=${workerId}`, { - headers: { - 'x-playwright-access-key': process.env.PLAYWRIGHT_GRID_ACCESS_KEY!, - } - }); - dispatcherConnection.onmessage = message => ws.send(JSON.stringify(message)); - ws.on('upgrade', response => { - const headers: Record = {}; - for (let i = 0; i < response.rawHeaders.length; i += 2) - headers[response.rawHeaders[i]] = response.rawHeaders[i + 1]; - - browserName = headers['x-playwright-browser'] as any || 'chromium'; - launchOptions = JSON.parse(headers['x-playwright-launch-options'] || '{}'); - proxyPattern = headers['x-playwright-proxy'] || ''; - - log({ browserName, launchOptions, proxyPattern }); - }); - ws.once('open', () => { - log('worker opened'); - new RootDispatcher(dispatcherConnection, async (rootScope, { sdkLanguage }) => { - const playwright = createPlaywright({ sdkLanguage }); - if (proxyPattern) - socksProxy = await createOwnedSocksProxy(proxyPattern, playwright); - const browser = await playwright[browserName].launch(serverSideCallMetadata(), launchOptions); - return new PlaywrightDispatcher(rootScope, playwright, socksProxy, browser); - }); - }); - ws.on('message', message => dispatcherConnection.dispatch(JSON.parse(message.toString()))); - ws.on('error', error => { - log('socket error'); - dispose(); - }); - ws.on('close', async () => { - log('worker deleted'); - dispose(); - }); - } -} - -async function createOwnedSocksProxy(proxyPattern: string, playwright: Playwright): Promise { - if (!proxyPattern) - return; - const socksProxy = new SocksProxy(); - socksProxy.setPattern(proxyPattern); - playwright.options.socksProxyPort = await socksProxy.listen(0); - log(`started socks proxy on port ${playwright.options.socksProxyPort}`); - return socksProxy; -} - -new Worker(); diff --git a/tests/config/testMode.ts b/tests/config/testMode.ts index a16d5f7d5da63..02623b6dce5bc 100644 --- a/tests/config/testMode.ts +++ b/tests/config/testMode.ts @@ -17,7 +17,7 @@ import { start } from '../../packages/playwright-core/lib/outofprocess'; import type { Playwright } from '../../packages/playwright-core/lib/client/playwright'; -export type TestModeName = 'default' | 'driver' | 'service' | 'service2' | 'service-grid'; +export type TestModeName = 'default' | 'driver' | 'service' | 'service2'; interface TestMode { setup(): Promise; diff --git a/tests/library/playwright.config.ts b/tests/library/playwright.config.ts index 212111d2a80b2..8ba9e1ac6039d 100644 --- a/tests/library/playwright.config.ts +++ b/tests/library/playwright.config.ts @@ -79,35 +79,6 @@ if (mode === 'service2') { }; } -if (mode === 'service-grid') { - process.env.NODE_EXTRA_CA_CERTS = require.resolve('../../packages/playwright-grid/https/cert.pem'); - connectOptions = { - wsEndpoint: process.env.PLAYWRIGHT_GRID_URL || 'wss://localhost:3333', - timeout: 60 * 60 * 1000, - headers: { - 'x-playwright-access-key': process.env.PLAYWRIGHT_GRID_ACCESS_KEY || 'secret' - }, - exposeNetwork: '', - }; - webServer = process.env.PLAYWRIGHT_GRID_URL ? [] : [ - { - command: 'node ./cli.js grid --port=3333 --access-key=secret --https-cert=./https/cert.pem --https-key=./https/key.pem', - stdout: 'pipe', - url: 'https://localhost:3333/secret', - reuseExistingServer: !process.env.CI, - cwd: '../../packages/playwright-grid', - ignoreHTTPSErrors: true, - }, { - command: 'node ./cli.js node --grid=wss://localhost:3333 --access-key=secret --capacity=2', - cwd: '../../packages/playwright-grid', - }, - { - command: 'node ./cli.js node --grid=wss://localhost:3333 --access-key=secret --capacity=2', - cwd: '../../packages/playwright-grid', - } - ]; -} - const config: Config = { testDir, outputDir, diff --git a/utils/workspace.js b/utils/workspace.js index 3e1b1793fcabf..c5e94803c3fa2 100755 --- a/utils/workspace.js +++ b/utils/workspace.js @@ -213,12 +213,6 @@ const workspace = new Workspace(ROOT_PATH, [ path: path.join(ROOT_PATH, 'packages', 'playwright-ct-vue2'), files: ['LICENSE'], }), - new PWPackage({ - name: '@playwright/experimental-grid', - path: path.join(ROOT_PATH, 'packages', 'playwright-grid'), - files: ['LICENSE'], - noConsistent: true, - }), ]); if (require.main === module) {