diff --git a/.dockerignore b/.dockerignore index 6cb97d6e..d0cd2e8e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,14 +1,20 @@ /logs/ /target/ /var/ -/frontend/node_modules/ -/frontend/apps/remark42/node_modules/ -/frontend/apps/remark42/public/ /.vscode/ /.idea/ /bin/ /.git/ +# frontend files not needed in docker image +/frontend/node_modules/ +/frontend/apps/remark42/node_modules/ +/frontend/apps/remark42/public/ +# e2e tests arficats +/frontend/e2e/playwright-report/ +/frontend/e2e/playwright/.cache/ +/frontend/e2e/test-results/ + # source files docker-compose.yml compose-dev-backend.yml @@ -28,3 +34,4 @@ debug.test *.test remark42 /backend/var/ +/playwright-report/ diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 00000000..4a43bd66 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,34 @@ +name: e2e + +on: + push: + branches: [master] + paths: + - "frontend/apps/remark42/**" + - "frontend/e2e/**" + + pull_request: + branches: [master] + paths: + - "frontend/apps/remark42/**" + - "frontend/e2e/**" + +jobs: + tests: + name: Tests + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Build & run containers + id: tests + run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f compose-e2e-test.yml up --build --quiet-pull --exit-code-from tests + + - uses: actions/upload-artifact@v2 + if: always() + with: + name: playwright-report + path: ./playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 04a841f1..f22fe204 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ compose-private-frontend.yml compose-private.yml /backend/_example/*/vendor http-client.env.json +/playwright-report/ diff --git a/compose-e2e-test.yml b/compose-e2e-test.yml new file mode 100644 index 00000000..6592356d --- /dev/null +++ b/compose-e2e-test.yml @@ -0,0 +1,36 @@ +# compose for running e2e tests in CI +version: "2" + +services: + remark42: + build: + context: . + dockerfile: Dockerfile + args: + - SKIP_BACKEND_TEST=true + - SKIP_FRONTEND_TEST=true + + image: umputun/remark42:dev + container_name: "remark42" + + environment: + # remark42 hostname used for proper dev auth work for tests container which connects to it + # using the such hostname (unlike local development where 127.0.0.1 is used) + - REMARK_URL=http://remark42:8080 + - SECRET=12345 + - DEBUG=true + - ADMIN_PASSWD=password + - AUTH_DEV=true # activate local OAuth "dev" listening on http://remark42:8084 + - ADMIN_SHARED_ID=dev_user # set admin flag for default user on local oauth2 + - AUTH_ANON=true + - AUTH_EMAIL_ENABLE=true + volumes: + - ./var:/srv/var + + tests: + build: + context: ./frontend + dockerfile: Dockerfile.e2e + depends_on: [remark42] + volumes: + - ./playwright-report:/frontend/e2e/playwright-report diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..c90061b2 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,7 @@ +/.vscode/ +/.idea/ + +# e2e tests arficats +/e2e/playwright-report/ +/e2e/playwright/.cache/ +/e2e/test-results/ diff --git a/frontend/Dockerfile.e2e b/frontend/Dockerfile.e2e new file mode 100644 index 00000000..5582336f --- /dev/null +++ b/frontend/Dockerfile.e2e @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/playwright:v1.25.0-focal + +ENV CI true +WORKDIR /frontend + +COPY ./package.json ./pnpm-workspace.yaml ./pnpm-lock.yaml /frontend/ +COPY ./e2e/package.json /frontend/e2e/ +RUN corepack enable pnpm && pnpm install + +COPY ./e2e/playwright.config.ts /frontend/e2e/ +COPY ./e2e/tests /frontend/e2e/tests/ + +WORKDIR /frontend/e2e + +CMD pnpm test diff --git a/frontend/e2e/.gitignore b/frontend/e2e/.gitignore new file mode 100644 index 00000000..75e854d8 --- /dev/null +++ b/frontend/e2e/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/frontend/e2e/package.json b/frontend/e2e/package.json new file mode 100644 index 00000000..b5070848 --- /dev/null +++ b/frontend/e2e/package.json @@ -0,0 +1,19 @@ +{ + "name": "@remark42/tests", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "playwright test" + }, + "author": "Paul Mineev ", + "license": "MIT", + "devDependencies": { + "@playwright/test": "^1.25.0", + "@types/node": "^18.7.8", + "nanoid": "^4.0.0", + "playwright": "^1.25.0", + "ts-node": "^10.9.1", + "typescript": "^4.7.4" + } +} diff --git a/frontend/e2e/playwright.config.ts b/frontend/e2e/playwright.config.ts new file mode 100644 index 00000000..d9305242 --- /dev/null +++ b/frontend/e2e/playwright.config.ts @@ -0,0 +1,107 @@ +import type { PlaywrightTestConfig } from '@playwright/test' +import { devices } from '@playwright/test' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + 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: process.env.CI ? 'http://remark42:8080' : 'http://127.0.0.1:8080', + + /* 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: 'npm run start', + // port: 3000, + // }, +} + +export default config diff --git a/frontend/e2e/prettier.config.js b/frontend/e2e/prettier.config.js new file mode 100644 index 00000000..17755609 --- /dev/null +++ b/frontend/e2e/prettier.config.js @@ -0,0 +1,8 @@ +/** @type {import('prettier').Config} */ +module.exports = { + singleQuote: true, + semi: false, + arrowParens: 'always', + trailingComma: 'es5', + printWidth: 120, +} diff --git a/frontend/e2e/tests/post-comment.spec.ts b/frontend/e2e/tests/post-comment.spec.ts new file mode 100644 index 00000000..2d37bce5 --- /dev/null +++ b/frontend/e2e/tests/post-comment.spec.ts @@ -0,0 +1,30 @@ +import { test } from '@playwright/test' +import { nanoid } from 'nanoid' +import * as path from 'path' + +test.describe('Post comment', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/web/') + }) + + test('as dev user', async ({ page, browserName }) => { + const iframe = page.frameLocator('iframe[name]') + await iframe.locator('text=Sign In').click() + const [authPage] = await Promise.all([ + page.waitForEvent('popup'), + iframe.locator("[title='Sign In with Dev']").click(), + ]) + await authPage.locator('text=Authorize').click() + // triggers tab visibility and enables widget to re-render with auth state + await page.press('iframe[name]', 'Tab') + await iframe.locator('textarea').click() + const message = `Hello world! ${nanoid()}` + await iframe.locator('textarea').type(message) + await iframe.locator('text=Send').click() + // checks if comment was posted + iframe.locator(`text=${message}`).first() + await page.reload() + // checks if saved comment is visible + iframe.locator(`text=${message}`).first() + }) +}) diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 34fbacc3..68e96dee 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -213,6 +213,22 @@ importers: webpack-cli: 4.10.0_orc2ewcajya4tocnqelr4vzh7u webpack-dev-server: 4.9.3_77l47gmqkrqiei5z7sbwz5iaj4 + e2e: + specifiers: + '@playwright/test': ^1.25.0 + '@types/node': ^18.7.8 + nanoid: ^4.0.0 + playwright: ^1.25.0 + ts-node: ^10.9.1 + typescript: ^4.7.4 + devDependencies: + '@playwright/test': 1.25.0 + '@types/node': 18.7.8 + nanoid: 4.0.0 + playwright: 1.25.0 + ts-node: 10.9.1_itmtyrrie7wpjnrpwbb5uqyzwa + typescript: 4.7.4 + packages/api: specifiers: '@types/node': ^18.0.5 @@ -2346,6 +2362,15 @@ packages: resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==} dev: true + /@playwright/test/1.25.0: + resolution: {integrity: sha512-j4EZhTTQI3dBeWblE21EV//swwmBtOpIrLdOIJIRv4uqsLdHgBg1z+JtTg+AeC5o2bAXIE26kDNW5A0TimG8Bg==} + engines: {node: '>=14'} + hasBin: true + dependencies: + '@types/node': 18.7.8 + playwright-core: 1.25.0 + dev: true + /@polka/url/1.0.0-next.21: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true @@ -2880,6 +2905,10 @@ packages: resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==} dev: true + /@types/node/18.7.8: + resolution: {integrity: sha512-/YP55EMK2341JkODUb8DM9O0x1SIz2aBvyF33Uf1c76St3VpsMXEIW0nxuKkq/5cxnbz0RD9cfwNZHEAZQD3ag==} + dev: true + /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true @@ -8393,6 +8422,12 @@ packages: hasBin: true dev: true + /nanoid/4.0.0: + resolution: {integrity: sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==} + engines: {node: ^14 || ^16 || >=18} + hasBin: true + dev: true + /nanospinner/1.1.0: resolution: {integrity: sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA==} dependencies: @@ -8980,6 +9015,21 @@ packages: find-up: 4.1.0 dev: true + /playwright-core/1.25.0: + resolution: {integrity: sha512-kZ3Jwaf3wlu0GgU0nB8UMQ+mXFTqBIFz9h1svTlNduNKjnbPXFxw7mJanLVjqxHJRn62uBfmgBj93YHidk2N5Q==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /playwright/1.25.0: + resolution: {integrity: sha512-Z+pQNWI17Qx/tHhnmgMmPsptsisXpKgAnUvYv98kctlHUJaqMt2400P8kTw9vEPoC0xdxqu0JhxO7pDTmaaIKw==} + engines: {node: '>=14'} + hasBin: true + requiresBuild: true + dependencies: + playwright-core: 1.25.0 + dev: true + /postcss-attribute-case-insensitive/5.0.2_postcss@8.4.14: resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==} engines: {node: ^12 || ^14 || >=16} @@ -11138,6 +11188,37 @@ packages: yn: 3.1.1 dev: true + /ts-node/10.9.1_itmtyrrie7wpjnrpwbb5uqyzwa: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 18.7.8 + acorn: 8.7.1 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.7.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /ts-node/10.9.1_tdn3ypgnfy6bmey2q4hu5jonwi: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml index 3ff5faaa..345b3f44 100644 --- a/frontend/pnpm-workspace.yaml +++ b/frontend/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - "apps/*" + - "e2e" - "packages/*"