CI: Add Playwright UI e2e tests + CI (#614)

Adds Playwright for UI tests.
Basic Playwright test to login.
Playwright Github Action.

---------

Co-authored-by: sua yoo <sua@suayoo.com>
This commit is contained in:
Sara Tavares 2023-03-22 23:23:22 +00:00 committed by GitHub
parent e8f88a797b
commit b61592b5ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 2397 additions and 2398 deletions

View File

@ -0,0 +1,49 @@
name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
env:
DEV_PASSWORD: ${{ secrets.DEV_PASSWORD }}
API_BASE_URL: ${{ secrets.API_BASE_URL }}
working-directory: ./frontend
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'
cache-dependency-path: frontend/yarn.lock
- name: Install dependencies
working-directory: frontend
run: yarn install --frozen-lockfile
- name: Install Playwright Browsers
run: yarn add playwright && yarn playwright install --with-deps
working-directory: ./frontend
- name: Create env file
run: |
cd frontend
touch .env
echo DEV_PASSWORD="${{ secrets.DEV_PASSWORD }}" >> .env
echo API_BASE_URL=${{ secrets.API_BASE_URL }} >> .env
cat .env
- name: Build frontend
run: cd frontend && yarn build
id: build-frontend
- name: Run Playwright tests
run: cd frontend && yarn playwright test
- uses: actions/upload-artifact@v2
if: always()
with:
name: playwright-report
path: frontend/playwright-report/
retention-days: 30

7
frontend/.gitignore vendored
View File

@ -19,8 +19,11 @@
/dist/ /dist/
# dotenv # dotenv
.env.local .env
.env.*.local .env.*
storybook-static storybook-static
custom-elements.json custom-elements.json
/test-results/
/playwright-report/
/playwright/.cache/

View File

@ -0,0 +1,39 @@
const path = require("path");
require(path.resolve(process.cwd(), "./webpack.config.js"));
// for testing: for prod, the Dockerfile should have the official prod version used
const RWP_BASE_URL = process.env.RWP_BASE_URL || "https://replayweb.page/";
if (!process.env.API_BASE_URL) {
throw new Error(
"To run a dev frontend server, please set the API_BASE_URL pointing to your backend api server in '.env.local'"
);
}
const devBackendUrl = new URL(process.env.API_BASE_URL);
module.exports = {
proxy: {
"/api": {
target: devBackendUrl.href,
headers: {
Host: devBackendUrl.host,
},
ws: true,
},
"/data": {
target: devBackendUrl.href,
headers: {
Host: devBackendUrl.host,
},
},
},
// Serve replay service worker file
onBeforeSetupMiddleware: (server) => {
server.app.get("/replay/sw.js", (req, res) => {
res.set("Content-Type", "application/javascript");
res.send(`importScripts("${RWP_BASE_URL}sw.js")`);
});
},
};

View File

@ -33,6 +33,7 @@
"build": "webpack --config webpack.prod.js", "build": "webpack --config webpack.prod.js",
"build-dev": "webpack --mode development", "build-dev": "webpack --mode development",
"start": "webpack serve --mode=development --config webpack.dev.js", "start": "webpack serve --mode=development --config webpack.dev.js",
"serve": "node scripts/serve.js",
"lint": "eslint --fix \"src/**/*.{ts,js}\"", "lint": "eslint --fix \"src/**/*.{ts,js}\"",
"format": "prettier --write \"src/**/*.{ts,js,html,css,json}\"", "format": "prettier --write \"src/**/*.{ts,js,html,css,json}\"",
"localize:prepare": "yarn localize:extract && yarn localize:build", "localize:prepare": "yarn localize:extract && yarn localize:build",
@ -43,6 +44,7 @@
"@esm-bundle/chai": "^4.3.4-fix.0", "@esm-bundle/chai": "^4.3.4-fix.0",
"@lit/localize-tools": "^0.6.5", "@lit/localize-tools": "^0.6.5",
"@open-wc/testing": "^3.1.7", "@open-wc/testing": "^3.1.7",
"@playwright/test": "^1.31.2",
"@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-commonjs": "^18.0.0",
"@types/color": "^3.0.2", "@types/color": "^3.0.2",
"@types/lodash": "^4.14.178", "@types/lodash": "^4.14.178",
@ -55,6 +57,7 @@
"@web/test-runner": "^0.13.22", "@web/test-runner": "^0.13.22",
"@web/test-runner-playwright": "^0.8.8", "@web/test-runner-playwright": "^0.8.8",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.2",
"chromium": "^3.0.3",
"copy-webpack-plugin": "^9.1.0", "copy-webpack-plugin": "^9.1.0",
"css-loader": "^6.3.0", "css-loader": "^6.3.0",
"del-cli": "^4.0.1", "del-cli": "^4.0.1",

View File

@ -0,0 +1,91 @@
import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { channel: 'chrome' },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
webServer: {
command: "yarn serve",
port: 9871,
reuseExistingServer: !process.env.CI,
},
});

17
frontend/scripts/serve.js Normal file
View File

@ -0,0 +1,17 @@
// Serve app locally without building with webpack, e.g. for e2e
const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
const connectHistoryApiFallback = require("connect-history-api-fallback");
const devServerConfig = require("../config/dev-server.js");
const app = express();
devServerConfig.onBeforeSetupMiddleware({ app });
app.use("/", express.static("dist"));
Object.keys(devServerConfig.proxy).forEach((path) => {
app.use(path, createProxyMiddleware(devServerConfig.proxy[path]));
});
app.use(connectHistoryApiFallback());
app.listen(9871);

View File

@ -0,0 +1,28 @@
import { chromium } from 'playwright';
import { test, expect } from '@playwright/test';
test('test', async ({ baseURL }) => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(baseURL!);
await page.waitForLoadState('load');
await page.waitForSelector('input[name="username"]');
await page.click('input[name="username"]');
await page.fill('input[name="username"]', 'dev@webrecorder.net');
await page.click('input[name="password"]');
const devPassword = process.env.DEV_PASSWORD;
if (!devPassword) {
throw new Error('DEV_PASSWORD environment variable is not defined or null.');
}
await page.fill('input[name="password"]', devPassword);
await page.click('a:has-text("Log In")');
} finally {
await browser.close();
}
});

View File

@ -2,17 +2,8 @@ const path = require("path");
const { merge } = require("webpack-merge"); const { merge } = require("webpack-merge");
const [main, vnc] = require("./webpack.config.js"); const [main, vnc] = require("./webpack.config.js");
const devServerConfig = require("./config/dev-server.js");
// for testing: for prod, the Dockerfile should have the official prod version used
const RWP_BASE_URL = process.env.RWP_BASE_URL || "https://replayweb.page/";
if (!process.env.API_BASE_URL) {
throw new Error(
"To run a dev frontend server, please set the API_BASE_URL pointing to your backend api server in '.env.local'"
);
}
const devBackendUrl = new URL(process.env.API_BASE_URL);
const shoelaceAssetsSrcPath = path.resolve( const shoelaceAssetsSrcPath = path.resolve(
__dirname, __dirname,
"node_modules/@shoelace-style/shoelace/dist/assets" "node_modules/@shoelace-style/shoelace/dist/assets"
@ -38,29 +29,8 @@ module.exports = [
}, },
], ],
historyApiFallback: true, historyApiFallback: true,
proxy: { proxy: devServerConfig.proxy,
"/api": { onBeforeSetupMiddleware: devServerConfig.onBeforeSetupMiddleware,
target: devBackendUrl.href,
headers: {
Host: devBackendUrl.host,
},
ws: true,
},
"/data": {
target: devBackendUrl.href,
headers: {
Host: devBackendUrl.host,
},
},
},
// Serve replay service worker file
onBeforeSetupMiddleware: (server) => {
server.app.get("/replay/sw.js", (req, res) => {
res.set("Content-Type", "application/javascript");
res.send(`importScripts("${RWP_BASE_URL}sw.js")`);
});
},
port: 9870, port: 9870,
}, },
}), }),

File diff suppressed because it is too large Load Diff