diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..bdca5005c --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +PLAYWRIGHT_USERNAME= +PLAYWRIGHT_PASSWORD= diff --git a/.github/workflows/e2etest.yml b/.github/workflows/e2etest.yml index c7d0ee3ba..2710549a1 100644 --- a/.github/workflows/e2etest.yml +++ b/.github/workflows/e2etest.yml @@ -30,12 +30,11 @@ jobs: node-version: lts/* - name: Install dependencies run: npm ci - - name: Setup credentials - run: mkdir -p playwright/.auth - - name: Import Credentials - run: echo "${{ secrets.TEST_ACCOUNT_CREDS }}" | base64 --decode > playwright/.auth/creds.json - name: Run Playwright tests run: npm run e2e -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + env: + PLAYWRIGHT_USERNAME: ${{ secrets.TEST_ACCOUNT_USERNAME }} + PLAYWRIGHT_PASSWORD: ${{ secrets.TEST_ACCOUNT_PASSWORD }} - uses: actions/upload-artifact@v7 if: ${{ !cancelled() }} with: diff --git a/.gitignore b/.gitignore index c4df4fc58..164d071ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # See http://help.github.com/ignore-files/ for more about ignoring files. *.ts.* +.env # compiled output /dist /tmp @@ -57,4 +58,4 @@ docs/ /test-results/ /playwright-report/ /playwright/.cache/ -playwright/ \ No newline at end of file +playwright/ diff --git a/e2e/auth.setup.ts b/e2e/auth.setup.ts index 23be872f6..37b5e7702 100644 --- a/e2e/auth.setup.ts +++ b/e2e/auth.setup.ts @@ -1,28 +1,31 @@ -import { expect, test as setup } from '@playwright/test'; -import path from 'path'; -import fs from 'fs'; +import { chromium, expect } from '@playwright/test'; +import dotenv from 'dotenv'; +import { overrideUserCookieHeaders } from 'e2e/helpers'; + +async function globalSetup() { + // const { baseURL } = config.projects[0].use; + const browser = await chromium.launch(); + const page = await browser.newPage(); + console.log('Running auth setup function'); + dotenv.config({ quiet: true }); -// import { username, password } from '../playwright/.auth/creds.json'; -const authFile = path.join(__dirname, '../playwright/.auth/user.json'); -const credsFile = path.join(__dirname, '../playwright/.auth/creds.json'); -setup('authenticate', async ({ page }) => { - let userData = {}; - try { - const data = fs.readFileSync(credsFile, 'utf8'); - userData = JSON.parse(data); - } catch (_e) { - console.error('Error reading or parsing auth JSON file.'); - return; - } await page.goto('https://search.asf.alaska.edu'); const popupPromise = page.waitForEvent('popup'); - + if (!process.env['PLAYWRIGHT_USERNAME']) { + console.log('No credentials defined: Some tests may fail'); + return; + } + console.log('Signing in'); await page.getByRole('button', { name: 'Sign In' }).click(); const popup = await popupPromise; - await popup.getByLabel('username').fill(userData['username']); - await popup.getByLabel('password').fill(userData['password']); + await popup.getByLabel('username').fill(process.env['PLAYWRIGHT_USERNAME']); + await popup.getByLabel('password').fill(process.env['PLAYWRIGHT_PASSWORD']); + await overrideUserCookieHeaders(page); await popup.getByRole('button', { name: 'Log in' }).click(); await expect(page.getByRole('button', { name: 'Sign In' })).toHaveCount(0); + console.log('Sign in successfull'); + await page.context().storageState({ path: 'playwright/.auth/user.json' }); + await browser.close(); +} - await page.context().storageState({ path: authFile }); -}); +export default globalSetup; diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 2a554a8e9..2de47cfa3 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -5,3 +5,19 @@ export async function waitForASFAPIResponse(page: Page) { response.url().includes('output=jsonlite2'), ); } + +export async function overrideUserCookieHeaders(page: Page) { + await page.route('**appdata-**/info/cookie', async (route) => { + const response = await route.fetch(); + const url = new URL(page.url()).origin; + await route.fulfill({ + response, + headers: { + ...response.headers(), + 'Access-Control-Allow-Origin': url, + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': '*', + }, + }); + }); +} diff --git a/e2e/pages/auth.page.ts b/e2e/pages/auth.page.ts new file mode 100644 index 000000000..2c616c839 --- /dev/null +++ b/e2e/pages/auth.page.ts @@ -0,0 +1,11 @@ +import { test as base, Page } from '@playwright/test'; +import { overrideUserCookieHeaders } from 'e2e/helpers'; + +export const test = base.extend<{ loggedInPage: Page }>({ + loggedInPage: async ({ page }, use) => { + await overrideUserCookieHeaders(page); + + await use(page); + }, +}); +export { expect } from '@playwright/test'; diff --git a/e2e/profile/test.spec.ts b/e2e/profile/test.spec.ts new file mode 100644 index 000000000..d46c1b62a --- /dev/null +++ b/e2e/profile/test.spec.ts @@ -0,0 +1,9 @@ +import { test, expect } from 'e2e/pages/auth.page'; + +test('Profile: Logged In', { tag: '@auth' }, async ({ loggedInPage }) => { + loggedInPage.goto('/'); + + await expect( + loggedInPage.getByRole('button', { name: 'automatedtesting_fullaccess' }), + ).toBeVisible(); +}); diff --git a/package-lock.json b/package-lock.json index fedf67a2a..b80f6583f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,6 +96,7 @@ "@vitest/coverage-v8": "^4.0.17", "angular-eslint": "21.3.1", "csv-parse": "^6.2.1", + "dotenv": "^17.4.2", "esbuild": "^0.28.0", "eslint": "^9.35.0", "eslint-config-prettier": "^10.1.8", @@ -8705,6 +8706,19 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index a591303fe..06f7cfad2 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "@vitest/coverage-v8": "^4.0.17", "angular-eslint": "21.3.1", "csv-parse": "^6.2.1", + "dotenv": "^17.4.2", "esbuild": "^0.28.0", "eslint": "^9.35.0", "eslint-config-prettier": "^10.1.8", diff --git a/playwright.config.ts b/playwright.config.ts index 10883fbf3..840bf6eb8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,11 +1,5 @@ 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. */ @@ -35,11 +29,9 @@ export default defineConfig({ maxDiffPixelRatio: 0.2, }, }, - + globalSetup: './e2e/auth.setup', /* Configure projects for major browsers */ projects: [ - { name: 'setup', testMatch: /.*\.setup\.ts/ }, - { name: 'chromium', use: { @@ -47,19 +39,32 @@ export default defineConfig({ viewport: { width: 1920, height: 1080 }, timezoneId: 'America/New_York', bypassCSP: true, - // storageState: 'playwright/.auth/user.json', - // TODO: this makes all tests authenticated, we probably want to define test suites that use authentication instead of general projects }, + grepInvert: /@auth/, }, { name: 'firefox', use: { ...devices['Desktop Firefox'], + viewport: { width: 1920, height: 1080 }, timezoneId: 'America/New_York', bypassCSP: true, + }, + grepInvert: /@auth/, + }, + + { + name: 'chromium-auth', + use: { + ...devices['Desktop Chrome'], + trace: 'off', + storageState: 'playwright/.auth/user.json', viewport: { width: 1920, height: 1080 }, + timezoneId: 'America/New_York', + bypassCSP: true, }, + grep: /@auth/, }, // {