From f4a9f75fe9ec3a554d10f58bf16ae3eef07e676f Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Thu, 21 Mar 2024 14:21:24 -0500 Subject: [PATCH 01/10] Add Playwright Tests to Crossfeed. --- .../.github/workflows/playwright.yml | 28 + frontend/xfd_playwright/.gitignore | 14 + .../e2e/global-admin/home.spec.ts | 66 +++ .../e2e/global-admin/inventory.spec.ts | 72 +++ .../e2e/global-admin/vulnerabilities.spec.ts | 59 +++ frontend/xfd_playwright/global-setup.ts | 42 ++ frontend/xfd_playwright/package-lock.json | 125 +++++ frontend/xfd_playwright/package.json | 18 + frontend/xfd_playwright/playwright.config.ts | 50 ++ .../tests-examples/demo-todo-app.spec.ts | 489 ++++++++++++++++++ frontend/xfd_playwright/tests/example.spec.ts | 20 + 11 files changed, 983 insertions(+) create mode 100644 frontend/xfd_playwright/.github/workflows/playwright.yml create mode 100644 frontend/xfd_playwright/.gitignore create mode 100644 frontend/xfd_playwright/e2e/global-admin/home.spec.ts create mode 100644 frontend/xfd_playwright/e2e/global-admin/inventory.spec.ts create mode 100644 frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts create mode 100644 frontend/xfd_playwright/global-setup.ts create mode 100644 frontend/xfd_playwright/package-lock.json create mode 100644 frontend/xfd_playwright/package.json create mode 100644 frontend/xfd_playwright/playwright.config.ts create mode 100644 frontend/xfd_playwright/tests-examples/demo-todo-app.spec.ts create mode 100644 frontend/xfd_playwright/tests/example.spec.ts diff --git a/frontend/xfd_playwright/.github/workflows/playwright.yml b/frontend/xfd_playwright/.github/workflows/playwright.yml new file mode 100644 index 00000000..700c871a --- /dev/null +++ b/frontend/xfd_playwright/.github/workflows/playwright.yml @@ -0,0 +1,28 @@ +--- +name: Playwright Tests +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/frontend/xfd_playwright/.gitignore b/frontend/xfd_playwright/.gitignore new file mode 100644 index 00000000..5354ded6 --- /dev/null +++ b/frontend/xfd_playwright/.gitignore @@ -0,0 +1,14 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +storageState.json diff --git a/frontend/xfd_playwright/e2e/global-admin/home.spec.ts b/frontend/xfd_playwright/e2e/global-admin/home.spec.ts new file mode 100644 index 00000000..033d5273 --- /dev/null +++ b/frontend/xfd_playwright/e2e/global-admin/home.spec.ts @@ -0,0 +1,66 @@ +import { test, expect, Page } from '@playwright/test'; +import exp from 'constants'; + +test.describe.configure({ mode: 'serial' }); +let page: Page; + +test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + await page.goto('/'); +}); + +test.afterAll(async () => { + await page.close(); +}); +test('home', async () => { + // Expect home page to show Latest Vulnerabilities. + await expect( + page.getByRole('heading', { name: 'Latest Vulnerabilities' }) + ).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Open Vulnerabilities by Domain' }) + ).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Most Common Ports' }) + ).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Most Common Vulnerabilities' }) + ).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Severity Levels' }) + ).toBeVisible(); + await expect( + page.getByPlaceholder('Search for a domain, vuln') + ).toBeVisible(); + await expect(page.getByRole('link', { name: 'Inventory' })).toBeVisible(); + await page.screenshot({ path: 'test-results/img/global-admin/home.png' }); +}); + +test('Open Vulnerabilities by Domain', async () => { + await page.getByRole('button', { name: 'All' }).click(); + await page.screenshot({ + path: 'test-results/img/global-admin/open_vuln_all.png' + }); + if ( + (await page.getByRole('button', { name: 'Medium' }).isDisabled()) == false + ) { + await page.getByRole('button', { name: 'Medium' }).click(); + await page.screenshot({ + path: 'test-results/img/global-admin/open_vuln_medium.png' + }); + } + if ( + (await page.getByRole('button', { name: 'High' }).isDisabled()) == false + ) { + await page.getByRole('button', { name: 'High' }).click(); + await page.screenshot({ + path: 'test-results/img/global-admin/open_vuln_high.png' + }); + } + if ((await page.getByLabel('Go to next page').isDisabled()) == false) { + await page.getByLabel('Go to next page').click(); + } + if ((await page.getByLabel('Go to previous page').isDisabled()) == false) { + await page.getByLabel('Go to previous page').click(); + } +}); diff --git a/frontend/xfd_playwright/e2e/global-admin/inventory.spec.ts b/frontend/xfd_playwright/e2e/global-admin/inventory.spec.ts new file mode 100644 index 00000000..559b7679 --- /dev/null +++ b/frontend/xfd_playwright/e2e/global-admin/inventory.spec.ts @@ -0,0 +1,72 @@ +import { test, expect, chromium, Page } from '@playwright/test'; + +test.describe.configure({ mode: 'serial' }); +let page: Page; + +test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + await page.goto('/'); +}); + +test.afterAll(async () => { + await page.close(); +}); +test('Inventory', async () => { + await page.getByRole('link', { name: 'Inventory' }).click(); + await expect(page).toHaveURL('/inventory'); + await page.getByRole('button', { name: 'IP(s)' }).click(); + await page.getByRole('button', { name: 'Severity' }).click(); + await page.getByLabel('Sort by:').first().click(); + await page.getByRole('option', { name: 'Domain Name' }).click(); + await page.getByLabel('Sort by:').first().click(); + await page.getByRole('option', { name: 'IP' }).click(); + await page.getByLabel('Sort by:').first().click(); + await page.getByRole('option', { name: 'Last Seen' }).click(); + await page.getByLabel('Sort by:').first().click(); + await page.getByRole('option', { name: 'First Seen' }).click(); + await page.screenshot({ + path: 'test-results/img/global-admin/inventory.png' + }); +}); + +test('Domains', async () => { + await page.goto('/inventory'); + await page.getByRole('link', { name: 'All Domains' }).click(); + await expect(page).toHaveURL('/inventory/domains'); + if ((await page.getByLabel('Go to next page').isDisabled()) == false) { + await page.getByLabel('Go to next page').click(); + } + if ((await page.getByLabel('Go to previous page').isDisabled()) == false) { + await page.getByLabel('Go to previous page').click(); + } + await page.screenshot({ path: 'test-results/img/global-admin/domains.png' }); +}); + +test('Domain details', async () => { + await page.goto('/inventory/domains'); + await page.getByRole('row').nth(2).getByRole('link').click(); + await expect(page).toHaveURL(new RegExp('/inventory/domain/')); + await expect(page.getByText('IP:')).toBeVisible(); + await expect(page.getByText('First Seen:')).toBeVisible(); + await expect(page.getByText('Last Seen:')).toBeVisible(); + await expect(page.getByText('Organization:')).toBeVisible(); + await page.screenshot({ + path: 'test-results/img/global-admin/domain_details.png' + }); +}); + +test('Domains filter', async () => { + await page.goto('/inventory/domains'); + await page.locator('#organizationName').click(); + await page.locator('#organizationName').fill('Homeland'); + await page.locator('#organizationName').press('Enter'); + let rowCount = await page.getByRole('row').count(); + for (let it = 2; it < rowCount; it++) { + await expect( + page.getByRole('row').nth(it).getByRole('cell').nth(1) + ).toContainText('Homeland'); + } + await page.screenshot({ + path: 'test-results/img/global-admin/domain_filter.png' + }); +}); diff --git a/frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts b/frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts new file mode 100644 index 00000000..51164e06 --- /dev/null +++ b/frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts @@ -0,0 +1,59 @@ +import { test, expect, chromium, Page } from '@playwright/test'; + +test.describe.configure({ mode: 'serial' }); +let page: Page; + +test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + await page.goto('/'); +}); + +test.afterAll(async () => { + await page.close(); +}); + +test('Vulnerabilities', async () => { + await page.getByRole('link', { name: 'Inventory' }).click(); + await page.getByRole('link', { name: 'All Vulnerabilities' }).click(); + await expect(page).toHaveURL('/inventory/vulnerabilities'); + if ((await page.getByLabel('Go to next page').isDisabled()) == false) { + await page.getByLabel('Go to next page').click(); + } + if ((await page.getByLabel('Go to previous page').isDisabled()) == false) { + await page.getByLabel('Go to previous page').click(); + } + await page.screenshot({ + path: 'test-results/img/global-admin/vulnerabilities.png' + }); +}); + +test('Vulnerability details NIST', async () => { + await page.goto('/inventory/vulnerabilities'); + const newTabPromise = page.waitForEvent('popup'); + await page.getByRole('row').nth(2).getByRole('link').nth(0).click(); + const newTab = await newTabPromise; + await newTab.waitForLoadState(); + await expect(newTab).toHaveURL( + new RegExp('^https://nvd.nist.gov/vuln/detail/') + ); +}); + +test('Domain details link', async () => { + await page.goto('/inventory/vulnerabilities'); + await page.getByRole('row').nth(2).getByRole('link').nth(1).click(); + await expect(page).toHaveURL(new RegExp('/inventory/domain/')); +}); + +test('Vulnerability details', async () => { + await page.goto('/inventory/vulnerabilities'); + await page.getByRole('row').nth(2).getByRole('link').nth(2).click(); + await expect(page).toHaveURL(new RegExp('/inventory/vulnerability/')); + await expect(page.getByRole('heading', { name: 'Overview' })).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Installed (Known) Products' }) + ).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Provenance' })).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Vulnerability Detection History' }) + ).toBeVisible(); +}); diff --git a/frontend/xfd_playwright/global-setup.ts b/frontend/xfd_playwright/global-setup.ts new file mode 100644 index 00000000..7130526f --- /dev/null +++ b/frontend/xfd_playwright/global-setup.ts @@ -0,0 +1,42 @@ +import { chromium, FullConfig, test as setup } from '@playwright/test'; +import * as OTPAuth from 'otpauth'; +import * as dotenv from 'dotenv'; + +dotenv.config(); + +const authFile = './storageState.json'; + +let totp = new OTPAuth.TOTP({ + issuer: process.env.CROSSFEED_2FA_ISSUER, + label: 'Crossfeed', + algorithm: 'SHA1', + digits: 6, + period: 30, + secret: process.env.CROSSFEED_2FA_SECRET +}); + +async function globalSetup(config: FullConfig) { + const { baseURL, storageState } = config.projects[0].use; + const browser = await chromium.launch(); + const page = await browser.newPage(); + + //Log in with credentials. + await page.goto(String(process.env.CROSSFEED_URL)); + await page + .getByPlaceholder('Enter your email address') + .fill(String(process.env.CROSSFEED_USERNAME)); + await page + .getByPlaceholder('Enter your password') + .fill(String(process.env.CROSSFEED_PASSWORD)); + await page.getByRole('button', { name: 'Sign in' }).click(); + await page + .getByPlaceholder('Enter code from your authenticator app') + .fill(totp.generate()); + await page.getByRole('button', { name: 'Confirm' }).click(); + //Wait for storageState to write to json file for other tests to use. + await page.waitForTimeout(1000); + await page.context().storageState({ path: authFile }); + await page.close(); +} + +export default globalSetup; diff --git a/frontend/xfd_playwright/package-lock.json b/frontend/xfd_playwright/package-lock.json new file mode 100644 index 00000000..39b36fb4 --- /dev/null +++ b/frontend/xfd_playwright/package-lock.json @@ -0,0 +1,125 @@ +{ + "lockfileVersion": 3, + "name": "xfd_playwright", + "packages": { + "": { + "dependencies": { + "dotenv": "^16.4.5", + "otpauth": "^9.2.2" + }, + "devDependencies": { + "@playwright/test": "^1.41.2", + "@types/node": "^20.11.20" + }, + "license": "ISC", + "name": "xfd_playwright", + "version": "1.0.0" + }, + "node_modules/@playwright/test": { + "bin": { + "playwright": "cli.js" + }, + "dependencies": { + "playwright": "1.41.2" + }, + "dev": true, + "engines": { + "node": ">=16" + }, + "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", + "version": "1.41.2" + }, + "node_modules/@types/node": { + "dependencies": { + "undici-types": "~5.26.4" + }, + "dev": true, + "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", + "version": "20.11.20" + }, + "node_modules/dotenv": { + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + }, + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "version": "16.4.5" + }, + "node_modules/fsevents": { + "dev": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + }, + "hasInstallScript": true, + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true, + "os": [ + "darwin" + ], + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "version": "2.3.2" + }, + "node_modules/jssha": { + "engines": { + "node": "*" + }, + "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", + "version": "3.3.1" + }, + "node_modules/otpauth": { + "dependencies": { + "jssha": "~3.3.1" + }, + "funding": { + "url": "https://github.com/hectorm/otpauth?sponsor=1" + }, + "integrity": "sha512-2VcnYRUmq1dNckIfySNYP32ITWp1bvTeAEW0BSCR6G3GBf3a5zb9E+ubY62t3Dma9RjoHlvd7QpmzHfJZRkiNg==", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.2.2.tgz", + "version": "9.2.2" + }, + "node_modules/playwright": { + "bin": { + "playwright": "cli.js" + }, + "dependencies": { + "playwright-core": "1.41.2" + }, + "dev": true, + "engines": { + "node": ">=16" + }, + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", + "optionalDependencies": { + "fsevents": "2.3.2" + }, + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "version": "1.41.2" + }, + "node_modules/playwright-core": { + "bin": { + "playwright-core": "cli.js" + }, + "dev": true, + "engines": { + "node": ">=16" + }, + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "version": "1.41.2" + }, + "node_modules/undici-types": { + "dev": true, + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "version": "5.26.5" + } + }, + "requires": true, + "version": "1.0.0" +} diff --git a/frontend/xfd_playwright/package.json b/frontend/xfd_playwright/package.json new file mode 100644 index 00000000..38e48e97 --- /dev/null +++ b/frontend/xfd_playwright/package.json @@ -0,0 +1,18 @@ +{ + "author": "", + "dependencies": { + "dotenv": "^16.4.5", + "otpauth": "^9.2.2" + }, + "description": "", + "devDependencies": { + "@playwright/test": "^1.41.2", + "@types/node": "^20.11.20" + }, + "keywords": [], + "license": "ISC", + "main": "index.js", + "name": "xfd_playwright", + "scripts": {}, + "version": "1.0.0" +} diff --git a/frontend/xfd_playwright/playwright.config.ts b/frontend/xfd_playwright/playwright.config.ts new file mode 100644 index 00000000..d8bc1e26 --- /dev/null +++ b/frontend/xfd_playwright/playwright.config.ts @@ -0,0 +1,50 @@ +import { defineConfig, devices } from '@playwright/test'; +import dotenv from 'dotenv'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ + +dotenv.config(); + +export default defineConfig({ + globalSetup: './global-setup', + testDir: './e2e', + /* 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: [ + ['list', { printSteps: true }], + ['json', { outputFile: 'test-results/test-results.json' }], + ['html', { open: 'always' }] + ], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.CROSSFEED_URL, + storageState: 'storageState.json', + + /* 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'] } + } + ] +}); diff --git a/frontend/xfd_playwright/tests-examples/demo-todo-app.spec.ts b/frontend/xfd_playwright/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 00000000..775ea003 --- /dev/null +++ b/frontend/xfd_playwright/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,489 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([TODO_ITEMS[0]]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ + page + }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ + page + }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count'); + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass([ + 'completed', + 'completed', + 'completed' + ]); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ + page + }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ + page + }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue( + TODO_ITEMS[1] + ); + await secondTodo + .getByRole('textbox', { name: 'Edit' }) + .fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect( + todoItem.locator('label', { + hasText: TODO_ITEMS[1] + }) + ).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems + .nth(1) + .getByRole('textbox', { name: 'Edit' }) + .fill('buy some sausages'); + await todoItems + .nth(1) + .getByRole('textbox', { name: 'Edit' }) + .dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems + .nth(1) + .getByRole('textbox', { name: 'Edit' }) + .fill(' buy some sausages '); + await todoItems + .nth(1) + .getByRole('textbox', { name: 'Edit' }) + .press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ + page + }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems + .nth(1) + .getByRole('textbox', { name: 'Edit' }) + .press('Enter'); + + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems + .nth(1) + .getByRole('textbox', { name: 'Edit' }) + .fill('buy some sausages'); + await todoItems + .nth(1) + .getByRole('textbox', { name: 'Edit' }) + .press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count'); + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect( + page.getByRole('button', { name: 'Clear completed' }) + ).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ + page + }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect( + page.getByRole('button', { name: 'Clear completed' }) + ).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass( + 'selected' + ); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction((e) => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage( + page: Page, + expected: number +) { + return await page.waitForFunction((e) => { + return ( + JSON.parse(localStorage['react-todos']).filter( + (todo: any) => todo.completed + ).length === e + ); + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction((t) => { + return JSON.parse(localStorage['react-todos']) + .map((todo: any) => todo.title) + .includes(t); + }, title); +} diff --git a/frontend/xfd_playwright/tests/example.spec.ts b/frontend/xfd_playwright/tests/example.spec.ts new file mode 100644 index 00000000..2a6048e1 --- /dev/null +++ b/frontend/xfd_playwright/tests/example.spec.ts @@ -0,0 +1,20 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://localhost/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Crossfeed/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect( + page.getByRole('heading', { name: 'Installation' }) + ).toBeVisible(); +}); From a3708ae840d9496fe6003d31684fc79b30ec23c7 Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Mon, 25 Mar 2024 14:32:49 -0500 Subject: [PATCH 02/10] Change playwright.yml to run in a Docker container with Github Actions. --- .../.github/workflows/playwright.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/frontend/xfd_playwright/.github/workflows/playwright.yml b/frontend/xfd_playwright/.github/workflows/playwright.yml index 700c871a..41ea832b 100644 --- a/frontend/xfd_playwright/.github/workflows/playwright.yml +++ b/frontend/xfd_playwright/.github/workflows/playwright.yml @@ -2,13 +2,15 @@ name: Playwright Tests on: push: - branches: [main, master] + branches: [develop] pull_request: - branches: [main, master] + branches: [develop] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.42.1-jammy steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 @@ -16,13 +18,7 @@ jobs: node-version: 18 - name: Install dependencies run: npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Run Playwright tests + - name: Run your tests run: npx playwright test - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 + env: + HOME: /root From e581670adf040b21349c9a7b4720a2ead3551693 Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Mon, 25 Mar 2024 15:28:41 -0500 Subject: [PATCH 03/10] Modify playwright.yml to run on all pull_request branches. --- frontend/xfd_playwright/.github/workflows/playwright.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/xfd_playwright/.github/workflows/playwright.yml b/frontend/xfd_playwright/.github/workflows/playwright.yml index 41ea832b..a235ba3a 100644 --- a/frontend/xfd_playwright/.github/workflows/playwright.yml +++ b/frontend/xfd_playwright/.github/workflows/playwright.yml @@ -1,10 +1,9 @@ --- name: Playwright Tests on: - push: - branches: [develop] pull_request: - branches: [develop] + branches: + - '*' jobs: test: timeout-minutes: 60 From 1242464be1403ec25bdc86919cb9df1d4f239ba9 Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Mon, 25 Mar 2024 15:43:35 -0500 Subject: [PATCH 04/10] Modify regular expression to validate opening to the nist.gov website. --- .../xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts b/frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts index 51164e06..afc1560c 100644 --- a/frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts +++ b/frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts @@ -34,7 +34,7 @@ test('Vulnerability details NIST', async () => { const newTab = await newTabPromise; await newTab.waitForLoadState(); await expect(newTab).toHaveURL( - new RegExp('^https://nvd.nist.gov/vuln/detail/') + new RegExp('^https://nvd\\.nist.gov/vuln/detail/') ); }); From 2ae45d6f32b17f77db57afd185d08b59228a9c0f Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Tue, 2 Apr 2024 14:25:12 -0500 Subject: [PATCH 05/10] Change version of Playwright docker image. --- frontend/xfd_playwright/.github/workflows/playwright.yml | 6 ++---- frontend/xfd_playwright/e2e/global-admin/home.spec.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/xfd_playwright/.github/workflows/playwright.yml b/frontend/xfd_playwright/.github/workflows/playwright.yml index a235ba3a..6c23b4d8 100644 --- a/frontend/xfd_playwright/.github/workflows/playwright.yml +++ b/frontend/xfd_playwright/.github/workflows/playwright.yml @@ -1,15 +1,13 @@ --- name: Playwright Tests on: - pull_request: - branches: - - '*' + deployment_status: jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.42.1-jammy + image: mcr.microsoft.com/playwright:v1.41.2-jammy steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/frontend/xfd_playwright/e2e/global-admin/home.spec.ts b/frontend/xfd_playwright/e2e/global-admin/home.spec.ts index 033d5273..1e1795e2 100644 --- a/frontend/xfd_playwright/e2e/global-admin/home.spec.ts +++ b/frontend/xfd_playwright/e2e/global-admin/home.spec.ts @@ -30,7 +30,7 @@ test('home', async () => { page.getByRole('heading', { name: 'Severity Levels' }) ).toBeVisible(); await expect( - page.getByPlaceholder('Search for a domain, vuln') + page.getByPlaceholder('Search a domain, vuln, port, service, IP') ).toBeVisible(); await expect(page.getByRole('link', { name: 'Inventory' })).toBeVisible(); await page.screenshot({ path: 'test-results/img/global-admin/home.png' }); From b26eaa8ccc108e69faccc50d2111765b49ac7f63 Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Fri, 5 Apr 2024 16:06:11 -0500 Subject: [PATCH 06/10] Move Playwright tests to top level of XFD repo, make changes needed for Github Actions. --- .github/workflows/playwright.yml | 36 +++++++++++++++++++ .../.github/workflows/playwright.yml | 21 ----------- .../xfd_playwright => playwright}/.gitignore | 0 .../e2e/global-admin/home.spec.ts | 0 .../e2e/global-admin/inventory.spec.ts | 0 .../e2e/global-admin/vulnerabilities.spec.ts | 0 .../global-setup.ts | 10 +++--- .../package-lock.json | 0 .../package.json | 0 .../playwright.config.ts | 2 +- .../tests-examples/demo-todo-app.spec.ts | 0 .../tests/example.spec.ts | 0 12 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/playwright.yml delete mode 100644 frontend/xfd_playwright/.github/workflows/playwright.yml rename {frontend/xfd_playwright => playwright}/.gitignore (100%) rename {frontend/xfd_playwright => playwright}/e2e/global-admin/home.spec.ts (100%) rename {frontend/xfd_playwright => playwright}/e2e/global-admin/inventory.spec.ts (100%) rename {frontend/xfd_playwright => playwright}/e2e/global-admin/vulnerabilities.spec.ts (100%) rename {frontend/xfd_playwright => playwright}/global-setup.ts (81%) rename {frontend/xfd_playwright => playwright}/package-lock.json (100%) rename {frontend/xfd_playwright => playwright}/package.json (100%) rename {frontend/xfd_playwright => playwright}/playwright.config.ts (97%) rename {frontend/xfd_playwright => playwright}/tests-examples/demo-todo-app.spec.ts (100%) rename {frontend/xfd_playwright => playwright}/tests/example.spec.ts (100%) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 00000000..8962bc42 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,36 @@ +--- +name: Playwright Tests +on: + deployment_status: + paths: + - 'playwright/**' + - '.github/workflows/playwright.yml' +defaults: + run: + working-directory: ./playwright +env: + PW_XFD_URL: ${{ vars.PW_XFD_URL }} + PW_XFD_USERNAME: ${{ secrets.PW_XFD_USERNAME }} + PW_XFD_PASSWORD: ${{ secrets.PW_XFD_PASSWORD }} + PW_XFD_2FA_ISSUER: ${{ secrets._PW_XFD_2FA_ISSUER }} + PW_XFD_2FA_SECRET: ${{ secrets.PW_XFD_2FA_SECRET }} + PW_XFD_USER_ROLE: ${{ vars.PW_XFD_USER_ROLE }} + +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.41.2-jammy + if: github.event.deployment_status.state == 'success' + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: cd ../playwright && npm ci + - name: Run your tests + run: npx playwright test + env: + HOME: /root diff --git a/frontend/xfd_playwright/.github/workflows/playwright.yml b/frontend/xfd_playwright/.github/workflows/playwright.yml deleted file mode 100644 index 6c23b4d8..00000000 --- a/frontend/xfd_playwright/.github/workflows/playwright.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Playwright Tests -on: - deployment_status: -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - container: - image: mcr.microsoft.com/playwright:v1.41.2-jammy - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Install dependencies - run: npm ci - - name: Run your tests - run: npx playwright test - env: - HOME: /root diff --git a/frontend/xfd_playwright/.gitignore b/playwright/.gitignore similarity index 100% rename from frontend/xfd_playwright/.gitignore rename to playwright/.gitignore diff --git a/frontend/xfd_playwright/e2e/global-admin/home.spec.ts b/playwright/e2e/global-admin/home.spec.ts similarity index 100% rename from frontend/xfd_playwright/e2e/global-admin/home.spec.ts rename to playwright/e2e/global-admin/home.spec.ts diff --git a/frontend/xfd_playwright/e2e/global-admin/inventory.spec.ts b/playwright/e2e/global-admin/inventory.spec.ts similarity index 100% rename from frontend/xfd_playwright/e2e/global-admin/inventory.spec.ts rename to playwright/e2e/global-admin/inventory.spec.ts diff --git a/frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts b/playwright/e2e/global-admin/vulnerabilities.spec.ts similarity index 100% rename from frontend/xfd_playwright/e2e/global-admin/vulnerabilities.spec.ts rename to playwright/e2e/global-admin/vulnerabilities.spec.ts diff --git a/frontend/xfd_playwright/global-setup.ts b/playwright/global-setup.ts similarity index 81% rename from frontend/xfd_playwright/global-setup.ts rename to playwright/global-setup.ts index 7130526f..410d250a 100644 --- a/frontend/xfd_playwright/global-setup.ts +++ b/playwright/global-setup.ts @@ -7,12 +7,12 @@ dotenv.config(); const authFile = './storageState.json'; let totp = new OTPAuth.TOTP({ - issuer: process.env.CROSSFEED_2FA_ISSUER, + issuer: process.env.PW_XFD_2FA_ISSUER, label: 'Crossfeed', algorithm: 'SHA1', digits: 6, period: 30, - secret: process.env.CROSSFEED_2FA_SECRET + secret: process.env.PW_XFD_2FA_SECRET }); async function globalSetup(config: FullConfig) { @@ -21,13 +21,13 @@ async function globalSetup(config: FullConfig) { const page = await browser.newPage(); //Log in with credentials. - await page.goto(String(process.env.CROSSFEED_URL)); + await page.goto(String(process.env.PW_XFD_URL)); await page .getByPlaceholder('Enter your email address') - .fill(String(process.env.CROSSFEED_USERNAME)); + .fill(String(process.env.PW_XFD_USERNAME)); await page .getByPlaceholder('Enter your password') - .fill(String(process.env.CROSSFEED_PASSWORD)); + .fill(String(process.env.PW_XFD_PASSWORD)); await page.getByRole('button', { name: 'Sign in' }).click(); await page .getByPlaceholder('Enter code from your authenticator app') diff --git a/frontend/xfd_playwright/package-lock.json b/playwright/package-lock.json similarity index 100% rename from frontend/xfd_playwright/package-lock.json rename to playwright/package-lock.json diff --git a/frontend/xfd_playwright/package.json b/playwright/package.json similarity index 100% rename from frontend/xfd_playwright/package.json rename to playwright/package.json diff --git a/frontend/xfd_playwright/playwright.config.ts b/playwright/playwright.config.ts similarity index 97% rename from frontend/xfd_playwright/playwright.config.ts rename to playwright/playwright.config.ts index d8bc1e26..2781f82f 100644 --- a/frontend/xfd_playwright/playwright.config.ts +++ b/playwright/playwright.config.ts @@ -33,7 +33,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: process.env.CROSSFEED_URL, + baseURL: process.env.PW_XFD_URL, storageState: 'storageState.json', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ diff --git a/frontend/xfd_playwright/tests-examples/demo-todo-app.spec.ts b/playwright/tests-examples/demo-todo-app.spec.ts similarity index 100% rename from frontend/xfd_playwright/tests-examples/demo-todo-app.spec.ts rename to playwright/tests-examples/demo-todo-app.spec.ts diff --git a/frontend/xfd_playwright/tests/example.spec.ts b/playwright/tests/example.spec.ts similarity index 100% rename from frontend/xfd_playwright/tests/example.spec.ts rename to playwright/tests/example.spec.ts From 5b2be56feac7b270c037aad0436b16182cabacc4 Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Tue, 9 Apr 2024 10:06:30 -0500 Subject: [PATCH 07/10] Add small change to RegEx and workflows yaml. --- .github/workflows/playwright.yml | 2 +- playwright/e2e/global-admin/vulnerabilities.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 8962bc42..9664a9b8 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -29,7 +29,7 @@ jobs: with: node-version: 18 - name: Install dependencies - run: cd ../playwright && npm ci + run: npm ci - name: Run your tests run: npx playwright test env: diff --git a/playwright/e2e/global-admin/vulnerabilities.spec.ts b/playwright/e2e/global-admin/vulnerabilities.spec.ts index afc1560c..ca92064f 100644 --- a/playwright/e2e/global-admin/vulnerabilities.spec.ts +++ b/playwright/e2e/global-admin/vulnerabilities.spec.ts @@ -34,7 +34,7 @@ test('Vulnerability details NIST', async () => { const newTab = await newTabPromise; await newTab.waitForLoadState(); await expect(newTab).toHaveURL( - new RegExp('^https://nvd\\.nist.gov/vuln/detail/') + new RegExp('^https://nvd\\.nist\\.gov/vuln/detail/') ); }); From 293878a5597c8193799f28e4c1c09fedb34fe0e7 Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Tue, 9 Apr 2024 12:38:54 -0500 Subject: [PATCH 08/10] Remove quotes from path strings in playwright.yml Co-authored-by: Shane Frasier --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 9664a9b8..e5aa5de3 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -3,8 +3,8 @@ name: Playwright Tests on: deployment_status: paths: - - 'playwright/**' - - '.github/workflows/playwright.yml' + - playwright/** + - .github/workflows/playwright.yml defaults: run: working-directory: ./playwright From 6452c695309d8b02996132e421cf8741e5748888 Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Tue, 9 Apr 2024 12:41:07 -0500 Subject: [PATCH 09/10] Update sort .gitignore entries and remove duplicates. Co-authored-by: dav3r --- playwright/.gitignore | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/playwright/.gitignore b/playwright/.gitignore index 5354ded6..dbcee752 100644 --- a/playwright/.gitignore +++ b/playwright/.gitignore @@ -1,14 +1,6 @@ -node_modules/ -/test-results/ -/playwright-report/ /blob-report/ -/playwright/.cache/ -/test-results/ /playwright-report/ -/blob-report/ /playwright/.cache/ /test-results/ -/playwright-report/ -/blob-report/ -/playwright/.cache/ +node_modules/ storageState.json From 8b33dd33e61d9832312a3aca9a10ce88aa894ceb Mon Sep 17 00:00:00 2001 From: JCantu248 Date: Tue, 9 Apr 2024 12:41:48 -0500 Subject: [PATCH 10/10] Sort environment variables alphabetically. Co-authored-by: dav3r --- .github/workflows/playwright.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e5aa5de3..d488d010 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -9,12 +9,12 @@ defaults: run: working-directory: ./playwright env: - PW_XFD_URL: ${{ vars.PW_XFD_URL }} - PW_XFD_USERNAME: ${{ secrets.PW_XFD_USERNAME }} - PW_XFD_PASSWORD: ${{ secrets.PW_XFD_PASSWORD }} PW_XFD_2FA_ISSUER: ${{ secrets._PW_XFD_2FA_ISSUER }} PW_XFD_2FA_SECRET: ${{ secrets.PW_XFD_2FA_SECRET }} + PW_XFD_PASSWORD: ${{ secrets.PW_XFD_PASSWORD }} + PW_XFD_URL: ${{ vars.PW_XFD_URL }} PW_XFD_USER_ROLE: ${{ vars.PW_XFD_USER_ROLE }} + PW_XFD_USERNAME: ${{ secrets.PW_XFD_USERNAME }} jobs: test: