diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index f0c6f1280b..1adecec62a 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -82,7 +82,7 @@ jobs: labels: ${{ steps.metalocal.outputs.labels }} cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max - cypress: + playwright: runs-on: [self-hosted, new] needs: [build] services: @@ -99,18 +99,14 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - container: cypress/browsers:node12.13.0-chrome78-ff70 steps: - uses: actions/checkout@v3 - name: "Docker" run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:latest)" id: docker-container - - name: "Cypress run" - uses: cypress-io/github-action@v4 + - name: "Playwright run" timeout-minutes: 10 - with: - working-directory: ./frontend - browser: chrome + run: cd frontend && npm ci @playwright/test && npx playwright install && npm run test - name: "Clean up" run: docker kill ${{ steps.docker-container.outputs.id }} if: always() diff --git a/frontend/.gitignore b/frontend/.gitignore index 08cad662a1..9ab832b0de 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -5,5 +5,5 @@ node_modules /package /src/lib/gen CaddyfileRemoteRuben -cypress/videos -cypress/screenshots +tests-out/ +storageState.json diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts deleted file mode 100644 index 76490462e4..0000000000 --- a/frontend/cypress.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from 'cypress' - -export default defineConfig({ - e2e: { - setupNodeEvents(on, config) { }, - baseUrl: 'http://localhost:8000', - }, -}) diff --git a/frontend/cypress/e2e/authentication.cy.ts b/frontend/cypress/e2e/authentication.cy.ts deleted file mode 100644 index f7bba67611..0000000000 --- a/frontend/cypress/e2e/authentication.cy.ts +++ /dev/null @@ -1,18 +0,0 @@ -// TODO: Should correctly handle exceptions -Cypress.on('uncaught:exception', (err, runnable) => { - // returning false here prevents Cypress from - // failing the test - return false -}) - -describe('Authentication', () => { - it('can login using email and password', () => { - cy.login('admin@windmill.dev', 'changeme') - cy.contains('Select a workspace') - }) - - it('should redirect to login page if user is not logged in', () => { - cy.visit(`/user/workspaces`) - cy.url().should('include', '/user/login') - }) -}) diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts deleted file mode 100644 index ea8b26669a..0000000000 --- a/frontend/cypress/support/commands.ts +++ /dev/null @@ -1,7 +0,0 @@ -Cypress.Commands.add('login', (email: string, password: string) => { - cy.visit(`/user/login`) - cy.get('#showPassword').click() - cy.get('#email').type('admin@windmill.dev') - cy.get('#password').type('changeme') - cy.get('.flex > .default-button').click() -}) diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts deleted file mode 100644 index 00a6a6e33d..0000000000 --- a/frontend/cypress/support/e2e.ts +++ /dev/null @@ -1,8 +0,0 @@ -import './commands' -declare global { - namespace Cypress { - interface Chainable { - login: (email: string, password: string) => Chainable - } - } -} diff --git a/frontend/cypress/tsconfig.json b/frontend/cypress/tsconfig.json deleted file mode 100644 index d38fedca20..0000000000 --- a/frontend/cypress/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["es5", "dom"], - "types": ["cypress"] - }, - "include": ["**/*.ts", "*.ts"] -} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d4f0d4d0c2..feca3546d4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -17,6 +17,7 @@ "vscode-ws-jsonrpc": "^1.0.1" }, "devDependencies": { + "@playwright/test": "^1.23.1", "@sveltejs/adapter-node": "^1.0.0-next.78", "@sveltejs/adapter-static": "^1.0.0-next.34", "@sveltejs/kit": "next", @@ -374,6 +375,22 @@ "node": ">= 8" } }, + "node_modules/@playwright/test": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.23.1.tgz", + "integrity": "sha512-dKplLPSYPZgnsBk1xxOophhpx3ZVg8DveoNJgLPe096lDCfmaIIreLsYF+4hqzy3PG61IP+aEnG5VAOjC3bhbA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.23.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/pluginutils": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", @@ -4571,6 +4588,18 @@ "node": ">=0.10.0" } }, + "node_modules/playwright-core": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.23.1.tgz", + "integrity": "sha512-9CXsE0gawph4KXl6oUaa0ehHRySZjHvly4TybcBXDvzK3N3o6L/eZ8Q6iVWUiMn0LLS5bRFxo1qEtOETlYJxjw==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/postcss": { "version": "8.4.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", @@ -7055,6 +7084,16 @@ "fastq": "^1.6.0" } }, + "@playwright/test": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.23.1.tgz", + "integrity": "sha512-dKplLPSYPZgnsBk1xxOophhpx3ZVg8DveoNJgLPe096lDCfmaIIreLsYF+4hqzy3PG61IP+aEnG5VAOjC3bhbA==", + "dev": true, + "requires": { + "@types/node": "*", + "playwright-core": "1.23.1" + } + }, "@rollup/pluginutils": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", @@ -10056,6 +10095,12 @@ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, + "playwright-core": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.23.1.tgz", + "integrity": "sha512-9CXsE0gawph4KXl6oUaa0ehHRySZjHvly4TybcBXDvzK3N3o6L/eZ8Q6iVWUiMn0LLS5bRFxo1qEtOETlYJxjw==", + "dev": true + }, "postcss": { "version": "8.4.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", diff --git a/frontend/package.json b/frontend/package.json index 62900aa405..07ca5f5998 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,15 +11,17 @@ "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .", "package": "svelte-kit package && cd package && rm README.md && rm README_DEV.md && sed -i -e 's/windmill/windmill-components/g' package.json", "generate-backend-client": "openapi --input ../backend/openapi.yaml --output ./src/lib/gen --useOptions", - "cypress:open": "cypress open", - "cypress:run": "cypress run" + "pretest": "tsc --incremental -p tests/tsconfig.json", + "test": "playwright test --config=tests-out/playwright.config.js" }, "devDependencies": { + "@playwright/test": "^1.23.1", "@sveltejs/adapter-node": "^1.0.0-next.78", "@sveltejs/adapter-static": "^1.0.0-next.34", "@sveltejs/kit": "next", "@tailwindcss/forms": "^0.5.1", "@tailwindcss/typography": "^0.5.0", + "@types/vscode": "^1.68.1", "@typescript-eslint/eslint-plugin": "^5.30.3", "@typescript-eslint/parser": "^5.30.3", "@zerodevx/svelte-toast": "^0.7.2", @@ -37,17 +39,16 @@ "simple-svelte-autocomplete": "^2.2.4", "stylelint-config-recommended": "^8.0.0", "svelte": "^3.42.6", + "svelte-awesome": "^3.0.0", "svelte-check": "^2.8.0", "svelte-highlight": "^6.1.2", + "svelte-markdown": "^0.2.1", "svelte-preprocess": "^4.9.8", + "svelte-split-pane": "^0.1.2", "svelte2tsx": "^0.5.11", "tailwindcss": "^3.1.4", "tslib": "^2.3.1", - "typescript": "^4.7.4", - "@types/vscode": "^1.68.1", - "svelte-awesome": "^3.0.0", - "svelte-markdown": "^0.2.1", - "svelte-split-pane": "^0.1.2" + "typescript": "^4.7.4" }, "type": "module", "dependencies": { diff --git a/frontend/src/routes/user/login@user.svelte b/frontend/src/routes/user/login@user.svelte index a9baa0ac80..53386860e4 100644 --- a/frontend/src/routes/user/login@user.svelte +++ b/frontend/src/routes/user/login@user.svelte @@ -134,7 +134,7 @@ />
- +
{/if} diff --git a/frontend/tests/authentication.spec.ts b/frontend/tests/authentication.spec.ts new file mode 100644 index 0000000000..e3fccf9392 --- /dev/null +++ b/frontend/tests/authentication.spec.ts @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + + +test('can login', async ({ page }) => { + await page.goto('http://localhost:8000/') + await expect(page.locator('text=Select a workspace')).toBeVisible() +}) diff --git a/frontend/tests/global-setup.ts b/frontend/tests/global-setup.ts new file mode 100644 index 0000000000..c18a947095 --- /dev/null +++ b/frontend/tests/global-setup.ts @@ -0,0 +1,19 @@ +import { chromium, FullConfig } from '@playwright/test' + +async function globalSetup(config: FullConfig) { + console.log('start global setup!') + const browser = await chromium.launch() + const page = await browser.newPage() + await page.goto('http://localhost:8000/user/login') + await page.locator('text=login without third-party').click() + await page.locator('#email').fill('admin@windmill.dev') + await page.locator('input[type="password"]').fill('changeme') + await page.locator('text="Login"').click() + await page.waitForResponse('http://localhost:8000/api/auth/login') + await page.context().storageState({ path: 'storageState.json' }) + await browser.close() + console.log('end global setup!') + +} + +export default globalSetup diff --git a/frontend/tests/playwright.config.ts b/frontend/tests/playwright.config.ts new file mode 100644 index 0000000000..fa8b203686 --- /dev/null +++ b/frontend/tests/playwright.config.ts @@ -0,0 +1,10 @@ +// playwright.config.ts +import type { PlaywrightTestConfig } from '@playwright/test' +const config: PlaywrightTestConfig = { + globalSetup: './global-setup', + use: { + // Tell all tests to load signed-in state from 'storageState.json'. + storageState: 'storageState.json' + } +} +export default config diff --git a/frontend/tests/tsconfig.json b/frontend/tests/tsconfig.json new file mode 100644 index 0000000000..e090ec200b --- /dev/null +++ b/frontend/tests/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "sourceMap": false, + "outDir": "../tests-out", + } +}