From deb0c72f4ce2db759e480f1cab2d9b96f76491ba Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 12:22:29 -0600 Subject: [PATCH 01/54] Testing e2e action --- .../ui/.github/workflows/playwright.yml | 27 + src/services/ui/.gitignore | 5 +- src/services/ui/e2e/example.spec.ts | 19 + src/services/ui/package.json | 6 +- src/services/ui/playwright.config.ts | 57 ++ src/services/ui/serverless.yml | 3 + .../ui/tests-examples/demo-todo-app.spec.ts | 489 ++++++++++++++++++ 7 files changed, 603 insertions(+), 3 deletions(-) create mode 100644 src/services/ui/.github/workflows/playwright.yml create mode 100644 src/services/ui/e2e/example.spec.ts create mode 100644 src/services/ui/playwright.config.ts create mode 100644 src/services/ui/tests-examples/demo-todo-app.spec.ts diff --git a/src/services/ui/.github/workflows/playwright.yml b/src/services/ui/.github/workflows/playwright.yml new file mode 100644 index 0000000000..29a2452a7d --- /dev/null +++ b/src/services/ui/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ play ] + pull_request: + branches: [ play ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Install dependencies + run: yarn + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run Playwright tests + run: yarn playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/src/services/ui/.gitignore b/src/services/ui/.gitignore index 3befe86990..e6cc649f1b 100644 --- a/src/services/ui/.gitignore +++ b/src/services/ui/.gitignore @@ -23,4 +23,7 @@ dist-ssr *.sln *.sw? -.env.local \ No newline at end of file +.env.local +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/src/services/ui/e2e/example.spec.ts b/src/services/ui/e2e/example.spec.ts new file mode 100644 index 0000000000..884f00d829 --- /dev/null +++ b/src/services/ui/e2e/example.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from "@playwright/test"; + +test("has title", async ({ page }) => { + await page.goto("/"); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/CMS OM Template/); +}); + +test("get issues link", async ({ page }) => { + await page.goto("/"); + + // Click the issues link. + await page.getByRole("button", { name: "Issues" }).click(); + await page.getByRole("link", { name: "All Issues" }).click(); + + // Expects the URL to contain intro. + await expect(page).toHaveURL(/.*issues/); +}); diff --git a/src/services/ui/package.json b/src/services/ui/package.json index f2ac569aeb..be3b4a9a96 100644 --- a/src/services/ui/package.json +++ b/src/services/ui/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite", + "e2e": "playwright test", "lint": "eslint 'src/**/*.{ts,tsx}'", "build": "tsc && vite build", "preview": "vite preview", @@ -29,6 +30,7 @@ "zod": "^3.21.4" }, "devDependencies": { + "@playwright/test": "^1.33.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/react": "^18.0.28", @@ -46,10 +48,10 @@ "postcss": "^8.4.21", "serverless-cloudfront-invalidate": "^1.12.2", "serverless-s3-sync": "^3.1.0", + "shared-types": "*", "tailwindcss": "^3.3.1", "typescript": "^4.9.3", "vite": "^4.2.0", - "vitest": "^0.30.1", - "shared-types": "*" + "vitest": "^0.30.1" } } diff --git a/src/services/ui/playwright.config.ts b/src/services/ui/playwright.config.ts new file mode 100644 index 0000000000..b9f67bbebb --- /dev/null +++ b/src/services/ui/playwright.config.ts @@ -0,0 +1,57 @@ +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: "./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: "html", + /* 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.APPURL || "http://localhost:5174", + + /* 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"] }, + }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/src/services/ui/serverless.yml b/src/services/ui/serverless.yml index 401fe0a9d6..8730b94832 100644 --- a/src/services/ui/serverless.yml +++ b/src/services/ui/serverless.yml @@ -43,3 +43,6 @@ custom: VITE_API_URL=${param:ApiUrl} """ > .env.local yarn build + commands: + connect: | + echo "connnection established blah!" diff --git a/src/services/ui/tests-examples/demo-todo-app.spec.ts b/src/services/ui/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000000..c06e84614c --- /dev/null +++ b/src/services/ui/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); +} From 146e4daef738ceb2eae531e9dc7d3db9db6c70b7 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 12:30:59 -0600 Subject: [PATCH 02/54] Testing e2e action --- .github/workflows/deploy.yml | 24 ++++++++++++++++++++++++ package.json | 3 ++- src/cli/run.ts | 9 +++++++++ turbo.json | 3 ++- yarn.lock | 17 ++++++++++++++++- 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index dfb8ffefc3..8f0fbd7d3c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -82,6 +82,30 @@ jobs: - name: Test run: yarn test-ci + e2e: + timeout-minutes: 60 + runs-on: ubuntu-latest + needs: + - deploy + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Install dependencies + run: yarn + - name: Install dependencies + run: yarn + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run Playwright tests + run: yarn playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 cfn-nag: runs-on: ubuntu-20.04 diff --git a/package.json b/package.json index 1e5ba2b0db..057db70ace 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "lint": "turbo lint", "build:cli": "turbo build:cli", "test:watch": "turbo test:watch", - "test:gui": "turbo test:gui" + "test:gui": "turbo test:gui", + "e2e": "turbo e2e" }, "repository": "https://github.com/Enterprise-CMCS/macpro-om-template", "workspaces": [ diff --git a/src/cli/run.ts b/src/cli/run.ts index 82e65e5ac5..21986afb3f 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -95,6 +95,15 @@ yargs(process.argv.slice(2)) ); } ) + .command( + "e2e", + "run e2e tests.", + {}, + async () => { + await install_deps_for_services(); + await runner.run_command_and_output(`e2e tests`, ["yarn", "e2e"], "."); + } + ) .command("test-gui", "open unit-testing gui for vitest.", {}, async () => { await install_deps_for_services(); await runner.run_command_and_output( diff --git a/turbo.json b/turbo.json index c6220ad6f1..8baa1f34b4 100644 --- a/turbo.json +++ b/turbo.json @@ -11,6 +11,7 @@ "cache": false }, "test:watch": {}, - "test:gui": {} + "test:gui": {}, + "e2e": {} } } diff --git a/yarn.lock b/yarn.lock index ee7b4a25d9..ed8b258276 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3132,6 +3132,16 @@ dependencies: "@octokit/openapi-types" "^16.0.0" +"@playwright/test@^1.33.0": + version "1.33.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.33.0.tgz#669ef859efb81b143dfc624eef99d1dd92a81b67" + integrity sha512-YunBa2mE7Hq4CfPkGzQRK916a4tuZoVx/EpLjeWlTVOnD4S2+fdaQZE0LJkbfhN5FTSKNLdcl7MoT5XB37bTkg== + dependencies: + "@types/node" "*" + playwright-core "1.33.0" + optionalDependencies: + fsevents "2.3.2" + "@pnpm/config.env-replace@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" @@ -7724,7 +7734,7 @@ fs2@^0.3.9: memoizee "^0.4.14" type "^2.1.0" -fsevents@^2.1.2, fsevents@~2.3.2: +fsevents@2.3.2, fsevents@^2.1.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -11563,6 +11573,11 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" +playwright-core@1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.33.0.tgz#269efe29a927cd6d144d05f3c2d2f72bd72447a1" + integrity sha512-aizyPE1Cj62vAECdph1iaMILpT0WUDCq3E6rW6I+dleSbBoGbktvJtzS6VHkZ4DKNEOG9qJpiom/ZxO+S15LAw== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" From 7bfb7c0b94ee9f85dab9b2b4b9b16df8ba26062c Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 12:40:24 -0600 Subject: [PATCH 03/54] remove connect command from ui service --- src/services/ui/serverless.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/services/ui/serverless.yml b/src/services/ui/serverless.yml index 8730b94832..e477f091e6 100644 --- a/src/services/ui/serverless.yml +++ b/src/services/ui/serverless.yml @@ -44,5 +44,3 @@ custom: """ > .env.local yarn build commands: - connect: | - echo "connnection established blah!" From 75024a9d76ba9052f67abb6dfa1243624539b031 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 12:55:46 -0600 Subject: [PATCH 04/54] remove connect command from ui service --- src/services/ui/serverless.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/ui/serverless.yml b/src/services/ui/serverless.yml index e477f091e6..401fe0a9d6 100644 --- a/src/services/ui/serverless.yml +++ b/src/services/ui/serverless.yml @@ -43,4 +43,3 @@ custom: VITE_API_URL=${param:ApiUrl} """ > .env.local yarn build - commands: From 22333064c9d93434546120d1080438373e27a7f8 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 13:26:07 -0600 Subject: [PATCH 05/54] adjust workflow --- .github/workflows/deploy.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8f0fbd7d3c..c5c631b1e8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -92,14 +92,16 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 16 - - name: Install dependencies - run: yarn - - name: Install dependencies - run: yarn - - name: Install Playwright Browsers - run: yarn playwright install --with-deps - - name: Run Playwright tests - run: yarn playwright test + # - name: Install dependencies + # run: yarn + # - name: Install dependencies + # run: yarn + # - name: Install Playwright Browsers + # run: yarn playwright install --with-deps + # - name: Run Playwright tests + # run: yarn playwright test + - name: Run e2e tests + run: run e2e - uses: actions/upload-artifact@v3 if: always() with: From a791c6aa90a3e2833dd3a0d29d31aee3b652f23d Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 13:35:31 -0600 Subject: [PATCH 06/54] exclude e2e from jest --- src/libs/vitest.config.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/vitest.config.ts b/src/libs/vitest.config.ts index 77a73cf2ed..40bf3ac1fd 100644 --- a/src/libs/vitest.config.ts +++ b/src/libs/vitest.config.ts @@ -1,5 +1,9 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ - test: {}, + test: { + exclude: [ + '**/e2e/**' + ], + }, }); From 6a11b199e2039d66d246a1242b5c9d65154c3388 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 13:46:52 -0600 Subject: [PATCH 07/54] exclude e2e from vitest config --- src/libs/vitest.config.ts | 6 +- .../ui/tests-examples/demo-todo-app.spec.ts | 489 ------------------ src/services/ui/vite.config.ts | 15 +- 3 files changed, 9 insertions(+), 501 deletions(-) delete mode 100644 src/services/ui/tests-examples/demo-todo-app.spec.ts diff --git a/src/libs/vitest.config.ts b/src/libs/vitest.config.ts index 40bf3ac1fd..77a73cf2ed 100644 --- a/src/libs/vitest.config.ts +++ b/src/libs/vitest.config.ts @@ -1,9 +1,5 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ - test: { - exclude: [ - '**/e2e/**' - ], - }, + test: {}, }); diff --git a/src/services/ui/tests-examples/demo-todo-app.spec.ts b/src/services/ui/tests-examples/demo-todo-app.spec.ts deleted file mode 100644 index c06e84614c..0000000000 --- a/src/services/ui/tests-examples/demo-todo-app.spec.ts +++ /dev/null @@ -1,489 +0,0 @@ -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/src/services/ui/vite.config.ts b/src/services/ui/vite.config.ts index 11a9de5fb5..5fc76f2672 100644 --- a/src/services/ui/vite.config.ts +++ b/src/services/ui/vite.config.ts @@ -1,15 +1,16 @@ -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react-swc' +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react-swc"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], test: { - environment: 'jsdom', - setupFiles: './testing/setup.ts', + environment: "jsdom", + setupFiles: "./testing/setup.ts", coverage: { - provider: 'istanbul', - reporter: 'json', + provider: "istanbul", + reporter: "json", }, + exclude: ["**/e2e/**"], }, -}) +}); From 1102f37de8b090beb88d37cde4bb96dcf0b465b3 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 14:06:19 -0600 Subject: [PATCH 08/54] update e2e wf action --- .github/workflows/deploy.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c5c631b1e8..6a08d275fc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -88,10 +88,14 @@ jobs: needs: - deploy steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 + - name: Checkout + uses: actions/checkout@v3 + - name: Setup + uses: ./.github/actions/setup + + # - uses: actions/setup-node@v3 + # with: + # node-version: 16 # - name: Install dependencies # run: yarn # - name: Install dependencies From 86057638d73a3fe6bf8a2f70287cb0debcebebeb Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 14:13:02 -0600 Subject: [PATCH 09/54] install prite browsers --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6a08d275fc..9309aafffe 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -100,8 +100,8 @@ jobs: # run: yarn # - name: Install dependencies # run: yarn - # - name: Install Playwright Browsers - # run: yarn playwright install --with-deps + - name: Install Playwright Browsers + run: yarn playwright install --with-deps # - name: Run Playwright tests # run: yarn playwright test - name: Run e2e tests From 85544373da1e62769fea135adaba770be1c3f489 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 14:31:11 -0600 Subject: [PATCH 10/54] add envar in action --- .github/workflows/deploy.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9309aafffe..06b221515c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -83,27 +83,19 @@ jobs: - name: Test run: yarn test-ci e2e: - timeout-minutes: 60 + timeout-minutes: 5 runs-on: ubuntu-latest needs: - deploy + environment: + APPURL: ${{ steps.deployment-data.outputs.APPURL }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup uses: ./.github/actions/setup - - # - uses: actions/setup-node@v3 - # with: - # node-version: 16 - # - name: Install dependencies - # run: yarn - # - name: Install dependencies - # run: yarn - name: Install Playwright Browsers run: yarn playwright install --with-deps - # - name: Run Playwright tests - # run: yarn playwright test - name: Run e2e tests run: run e2e - uses: actions/upload-artifact@v3 From c323004590d12623dcbf96e4fb67d271f637a970 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 14:48:35 -0600 Subject: [PATCH 11/54] export url in job --- .github/workflows/deploy.yml | 7 ++++++- src/services/ui/playwright.config.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 06b221515c..e1e2e1a8e1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -61,6 +61,11 @@ jobs: --stack-name $PROJECT-ui-infra-$STAGE_NAME \ --query Stacks[0].Outputs[0].OutputValue )" | tr -d \'\" >> $GITHUB_OUTPUT + echo "MY_VARIABLE=example" >> $GITHUB_ENV + - name: Export App URL + run: ${{ steps.deployment-data.outputs.APPURL }} >> $GITHUB_ENV + - name: Display URL + run: echo $APPURL test: runs-on: ubuntu-20.04 needs: @@ -88,7 +93,7 @@ jobs: needs: - deploy environment: - APPURL: ${{ steps.deployment-data.outputs.APPURL }} + baseurl: $APPURL steps: - name: Checkout uses: actions/checkout@v3 diff --git a/src/services/ui/playwright.config.ts b/src/services/ui/playwright.config.ts index b9f67bbebb..ceed25a642 100644 --- a/src/services/ui/playwright.config.ts +++ b/src/services/ui/playwright.config.ts @@ -24,7 +24,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.APPURL || "http://localhost:5174", + baseURL: process.env.baseurl || "http://localhost:5174", /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", From ffd061ac2e146f90d36f05d77fc11aeda6d073bf Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 14:50:22 -0600 Subject: [PATCH 12/54] export url in job --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e1e2e1a8e1..a64cffb821 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -93,7 +93,7 @@ jobs: needs: - deploy environment: - baseurl: $APPURL + baseurl: 'something.com' steps: - name: Checkout uses: actions/checkout@v3 From 05a5a60532a41b8e99b607e22349542c773de59c Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 14:52:11 -0600 Subject: [PATCH 13/54] export url in job --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a64cffb821..5b45617a5a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -93,7 +93,7 @@ jobs: needs: - deploy environment: - baseurl: 'something.com' + baseurl: ${{ github.APPURL }} steps: - name: Checkout uses: actions/checkout@v3 From 7d1ab0c01afb33e3c0c6651fe76f47c16a96231a Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 14:55:27 -0600 Subject: [PATCH 14/54] export url in job --- .github/workflows/deploy.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5b45617a5a..df8c3d326f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -61,7 +61,6 @@ jobs: --stack-name $PROJECT-ui-infra-$STAGE_NAME \ --query Stacks[0].Outputs[0].OutputValue )" | tr -d \'\" >> $GITHUB_OUTPUT - echo "MY_VARIABLE=example" >> $GITHUB_ENV - name: Export App URL run: ${{ steps.deployment-data.outputs.APPURL }} >> $GITHUB_ENV - name: Display URL @@ -92,8 +91,8 @@ jobs: runs-on: ubuntu-latest needs: - deploy - environment: - baseurl: ${{ github.APPURL }} + # environment: + # baseurl: ${{ github.APPURL }} steps: - name: Checkout uses: actions/checkout@v3 From 918e008557b1ff94c9234eb078b99307b720e879 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:01:05 -0600 Subject: [PATCH 15/54] export url in job --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index df8c3d326f..b091298bbb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -62,7 +62,7 @@ jobs: --query Stacks[0].Outputs[0].OutputValue )" | tr -d \'\" >> $GITHUB_OUTPUT - name: Export App URL - run: ${{ steps.deployment-data.outputs.APPURL }} >> $GITHUB_ENV + run: echo "APPURL=${{ steps.deployment-data.outputs.APPURL }}" >> $GITHUB_ENV - name: Display URL run: echo $APPURL test: From 47f1b3110e90563220440e01e5217c8fe55c0913 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:08:23 -0600 Subject: [PATCH 16/54] export url in job --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b091298bbb..9f8e3992ab 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -91,8 +91,8 @@ jobs: runs-on: ubuntu-latest needs: - deploy - # environment: - # baseurl: ${{ github.APPURL }} + environment: + baseurl: ${{ needs.deploy.outputs.APPURL }} steps: - name: Checkout uses: actions/checkout@v3 From dd5087821fb663d6da3db7723531ef569f569db6 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:13:45 -0600 Subject: [PATCH 17/54] export url in job --- .github/workflows/deploy.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9f8e3992ab..24ae8a416e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -88,11 +88,12 @@ jobs: run: yarn test-ci e2e: timeout-minutes: 5 - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - deploy environment: - baseurl: ${{ needs.deploy.outputs.APPURL }} + # baseurl: ${{ needs.deploy.outputs.APPURL }} + name: ${{ github.ref_name }} steps: - name: Checkout uses: actions/checkout@v3 From 66a8894dae2b54c66b75e5d27577b0c5e96b0efb Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:20:31 -0600 Subject: [PATCH 18/54] export url in job --- .github/workflows/deploy.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 24ae8a416e..11fe616e9c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -92,8 +92,7 @@ jobs: needs: - deploy environment: - # baseurl: ${{ needs.deploy.outputs.APPURL }} - name: ${{ github.ref_name }} + baseurl: ${{ needs.deploy.outputs.APPURL }} steps: - name: Checkout uses: actions/checkout@v3 From 3788da754c015014a82243a4a6fa64650c871b03 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:23:45 -0600 Subject: [PATCH 19/54] export url in job --- .github/workflows/deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 11fe616e9c..d995f2ba2b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-20.04 needs: - init - environment: + env: name: ${{ github.ref_name }} url: ${{ steps.deployment-data.outputs.APPURL }} steps: @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-20.04 needs: - deploy - environment: + env: name: ${{ github.ref_name }} steps: - name: Checkout @@ -91,7 +91,7 @@ jobs: runs-on: ubuntu-20.04 needs: - deploy - environment: + env: baseurl: ${{ needs.deploy.outputs.APPURL }} steps: - name: Checkout @@ -113,7 +113,7 @@ jobs: runs-on: ubuntu-20.04 needs: - deploy - environment: + env: name: ${{ github.ref_name }} steps: - name: Checkout From 36c36510b8e35012472dc782ebfee4559444b686 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:26:17 -0600 Subject: [PATCH 20/54] export url in job --- .github/workflows/deploy.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d995f2ba2b..3a27d2c57e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,7 +20,7 @@ permissions: jobs: init: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Validate stage name run: | @@ -29,10 +29,10 @@ jobs: fi deploy: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: - init - env: + environment: name: ${{ github.ref_name }} url: ${{ steps.deployment-data.outputs.APPURL }} steps: @@ -66,10 +66,10 @@ jobs: - name: Display URL run: echo $APPURL test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: - deploy - env: + environment: name: ${{ github.ref_name }} steps: - name: Checkout @@ -88,10 +88,10 @@ jobs: run: yarn test-ci e2e: timeout-minutes: 5 - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: - deploy - env: + environment: baseurl: ${{ needs.deploy.outputs.APPURL }} steps: - name: Checkout @@ -110,10 +110,10 @@ jobs: retention-days: 30 cfn-nag: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: - deploy - env: + environment: name: ${{ github.ref_name }} steps: - name: Checkout @@ -143,7 +143,7 @@ jobs: input_path: cftemplates release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: - test - cfn-nag @@ -159,7 +159,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} notify_of_failure: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: - release if: failure() From 96a2402c763889e529b65a0b78331e344ee379b7 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:27:27 -0600 Subject: [PATCH 21/54] export url in job --- .github/workflows/deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3a27d2c57e..201cd0ef7f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest needs: - init - environment: + env: name: ${{ github.ref_name }} url: ${{ steps.deployment-data.outputs.APPURL }} steps: @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest needs: - deploy - environment: + env: name: ${{ github.ref_name }} steps: - name: Checkout @@ -91,7 +91,7 @@ jobs: runs-on: ubuntu-latest needs: - deploy - environment: + env: baseurl: ${{ needs.deploy.outputs.APPURL }} steps: - name: Checkout @@ -113,7 +113,7 @@ jobs: runs-on: ubuntu-latest needs: - deploy - environment: + env: name: ${{ github.ref_name }} steps: - name: Checkout From 1e0a1fc561d22dc7793a469adb32d827048df9b3 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:30:28 -0600 Subject: [PATCH 22/54] export url in job --- .github/workflows/deploy.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 201cd0ef7f..11fe616e9c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,7 +20,7 @@ permissions: jobs: init: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Validate stage name run: | @@ -29,10 +29,10 @@ jobs: fi deploy: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - init - env: + environment: name: ${{ github.ref_name }} url: ${{ steps.deployment-data.outputs.APPURL }} steps: @@ -66,10 +66,10 @@ jobs: - name: Display URL run: echo $APPURL test: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - deploy - env: + environment: name: ${{ github.ref_name }} steps: - name: Checkout @@ -88,10 +88,10 @@ jobs: run: yarn test-ci e2e: timeout-minutes: 5 - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - deploy - env: + environment: baseurl: ${{ needs.deploy.outputs.APPURL }} steps: - name: Checkout @@ -110,10 +110,10 @@ jobs: retention-days: 30 cfn-nag: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - deploy - env: + environment: name: ${{ github.ref_name }} steps: - name: Checkout @@ -143,7 +143,7 @@ jobs: input_path: cftemplates release: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - test - cfn-nag @@ -159,7 +159,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} notify_of_failure: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: - release if: failure() From 02d07e709342c67c641a60ac5709a0b567617ca6 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:39:14 -0600 Subject: [PATCH 23/54] test --- .github/workflows/deploy.yml | 208 ++++++++++++++++++++++++++++++++--- 1 file changed, 193 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 11fe616e9c..58363b2e95 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,3 +1,179 @@ +# name: Deploy + +# on: +# push: +# branches: +# - "*" +# - "!skipci*" + +# concurrency: +# group: ${{ github.ref_name }}-group + +# env: +# STAGE_NAME: ${{ github.ref_name }} + +# permissions: +# id-token: write +# contents: write +# issues: write +# pull-requests: write + +# jobs: +# init: +# runs-on: ubuntu-20.04 +# steps: +# - name: Validate stage name +# run: | +# if [[ ! $STAGE_NAME =~ ^[a-z][a-z0-9-]*$ ]]; then +# echo "ERROR: Your branch name, $STAGE_NAME, is not a valid Serverless Framework stage name." && exit 1 +# fi + +# deploy: +# runs-on: ubuntu-20.04 +# needs: +# - init +# environment: +# name: ${{ github.ref_name }} +# url: ${{ steps.deployment-data.outputs.APPURL }} +# steps: +# - name: Checkout +# uses: actions/checkout@v3 + +# - uses: ./.github/actions/setup + +# - name: Configure AWS credentials +# uses: aws-actions/configure-aws-credentials@v2 +# with: +# role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} +# aws-region: us-east-1 +# role-duration-seconds: 10800 + +# - name: Deploy +# run: run deploy --stage $STAGE_NAME + +# - name: Set URL +# id: deployment-data +# run: | +# echo ${{ github.ref_name }} +# echo "APPURL=$( +# aws cloudformation \ +# --region us-east-1 describe-stacks \ +# --stack-name $PROJECT-ui-infra-$STAGE_NAME \ +# --query Stacks[0].Outputs[0].OutputValue +# )" | tr -d \'\" >> $GITHUB_OUTPUT +# - name: Export App URL +# run: echo "APPURL=${{ steps.deployment-data.outputs.APPURL }}" >> $GITHUB_ENV +# - name: Display URL +# run: echo $APPURL +# test: +# runs-on: ubuntu-20.04 +# needs: +# - deploy +# environment: +# name: ${{ github.ref_name }} +# steps: +# - name: Checkout +# uses: actions/checkout@v3 + +# - uses: ./.github/actions/setup + +# - name: Configure AWS credentials +# uses: aws-actions/configure-aws-credentials@v2 +# with: +# role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} +# aws-region: us-east-1 +# role-duration-seconds: 10800 + +# - name: Test +# run: yarn test-ci +# e2e: +# timeout-minutes: 5 +# runs-on: ubuntu-20.04 +# needs: +# - deploy +# environment: +# baseurl: HERE +# steps: +# - name: Checkout +# uses: actions/checkout@v3 +# - name: Setup +# uses: ./.github/actions/setup +# - name: Install Playwright Browsers +# run: yarn playwright install --with-deps +# - name: Run e2e tests +# run: run e2e +# - uses: actions/upload-artifact@v3 +# if: always() +# with: +# name: playwright-report +# path: playwright-report/ +# retention-days: 30 + +# cfn-nag: +# runs-on: ubuntu-20.04 +# needs: +# - deploy +# environment: +# name: ${{ github.ref_name }} +# steps: +# - name: Checkout +# uses: actions/checkout@v3 + +# - uses: ./.github/actions/setup + +# - name: Configure AWS credentials +# uses: aws-actions/configure-aws-credentials@v2 +# with: +# role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} +# aws-region: us-east-1 +# role-duration-seconds: 10800 + +# - name: Get CloudFormation templates +# id: getCfts +# run: | +# mkdir -p cftemplates +# stackList=(`aws cloudformation describe-stacks --query "Stacks[?Tags[?Key=='STAGE' && Value=='$STAGE_NAME'] && Tags[?Key=='PROJECT' && Value=='$PROJECT']].StackName" --output text`) +# for stack in "${stackList[@]}"; do +# aws cloudformation get-template --stack-name "$stack" --query TemplateBody > "cftemplates/${stack}.json" +# done + +# - name: Stelligent cfn_nag +# uses: stelligent/cfn_nag@v0.8.6 +# with: +# input_path: cftemplates + +# release: +# runs-on: ubuntu-20.04 +# needs: +# - test +# - cfn-nag +# steps: +# - name: Checkout +# uses: actions/checkout@v3 + +# - uses: ./.github/actions/setup + +# - name: Release +# run: npx semantic-release +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# notify_of_failure: +# runs-on: ubuntu-20.04 +# needs: +# - release +# if: failure() +# steps: +# - name: Slack Notification +# uses: rtCamp/action-slack-notify@v2 +# if: env.SLACK_WEBHOOK != '' && contains(fromJson('["master", "val", "production"]'), env.STAGE_NAME) +# env: +# SLACK_COLOR: ${{job.status}} +# SLACK_ICON: https://github.com/Enterprise-CMCS.png?size=48 +# SLACK_TITLE: Failure +# SLACK_USERNAME: ${{ github.repository }} - ${{job.status}} +# SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + name: Deploy on: @@ -65,6 +241,7 @@ jobs: run: echo "APPURL=${{ steps.deployment-data.outputs.APPURL }}" >> $GITHUB_ENV - name: Display URL run: echo $APPURL + test: runs-on: ubuntu-20.04 needs: @@ -86,28 +263,29 @@ jobs: - name: Test run: yarn test-ci + e2e: timeout-minutes: 5 runs-on: ubuntu-20.04 needs: - deploy environment: - baseurl: ${{ needs.deploy.outputs.APPURL }} + baseurl: ${{ steps.get-app-url.outputs.app-url }} steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup - uses: ./.github/actions/setup - - name: Install Playwright Browsers - run: yarn playwright install --with-deps - - name: Run e2e tests - run: run e2e - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 + - name: Checkout + uses: actions/checkout@v3 + - name: Setup + uses: ./.github/actions/setup + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run e2e tests + run: run e2e + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 cfn-nag: runs-on: ubuntu-20.04 From 2949e1315d7c1b6cf0fa110d02a56a45447a5eb9 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:41:42 -0600 Subject: [PATCH 24/54] test --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 58363b2e95..57c87b534c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -269,7 +269,7 @@ jobs: runs-on: ubuntu-20.04 needs: - deploy - environment: + env: baseurl: ${{ steps.get-app-url.outputs.app-url }} steps: - name: Checkout From d1725f6a70900a93e388b39070c343dcc4164dd9 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 15:43:26 -0600 Subject: [PATCH 25/54] test --- .github/workflows/deploy.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 57c87b534c..c4e389034a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -208,9 +208,8 @@ jobs: runs-on: ubuntu-20.04 needs: - init - environment: - name: ${{ github.ref_name }} - url: ${{ steps.deployment-data.outputs.APPURL }} + outputs: + app-url: ${{ steps.deployment-data.outputs.APPURL }} steps: - name: Checkout uses: actions/checkout@v3 @@ -270,7 +269,7 @@ jobs: needs: - deploy env: - baseurl: ${{ steps.get-app-url.outputs.app-url }} + baseurl: ${{ needs.deploy.outputs.app-url }} steps: - name: Checkout uses: actions/checkout@v3 From 7b011e55d176529f987e0782efe0685ba0654cf0 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 16:00:09 -0600 Subject: [PATCH 26/54] test --- .github/workflows/deploy.yml | 176 ----------------------------------- 1 file changed, 176 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c4e389034a..a2d9e02813 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,179 +1,3 @@ -# name: Deploy - -# on: -# push: -# branches: -# - "*" -# - "!skipci*" - -# concurrency: -# group: ${{ github.ref_name }}-group - -# env: -# STAGE_NAME: ${{ github.ref_name }} - -# permissions: -# id-token: write -# contents: write -# issues: write -# pull-requests: write - -# jobs: -# init: -# runs-on: ubuntu-20.04 -# steps: -# - name: Validate stage name -# run: | -# if [[ ! $STAGE_NAME =~ ^[a-z][a-z0-9-]*$ ]]; then -# echo "ERROR: Your branch name, $STAGE_NAME, is not a valid Serverless Framework stage name." && exit 1 -# fi - -# deploy: -# runs-on: ubuntu-20.04 -# needs: -# - init -# environment: -# name: ${{ github.ref_name }} -# url: ${{ steps.deployment-data.outputs.APPURL }} -# steps: -# - name: Checkout -# uses: actions/checkout@v3 - -# - uses: ./.github/actions/setup - -# - name: Configure AWS credentials -# uses: aws-actions/configure-aws-credentials@v2 -# with: -# role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} -# aws-region: us-east-1 -# role-duration-seconds: 10800 - -# - name: Deploy -# run: run deploy --stage $STAGE_NAME - -# - name: Set URL -# id: deployment-data -# run: | -# echo ${{ github.ref_name }} -# echo "APPURL=$( -# aws cloudformation \ -# --region us-east-1 describe-stacks \ -# --stack-name $PROJECT-ui-infra-$STAGE_NAME \ -# --query Stacks[0].Outputs[0].OutputValue -# )" | tr -d \'\" >> $GITHUB_OUTPUT -# - name: Export App URL -# run: echo "APPURL=${{ steps.deployment-data.outputs.APPURL }}" >> $GITHUB_ENV -# - name: Display URL -# run: echo $APPURL -# test: -# runs-on: ubuntu-20.04 -# needs: -# - deploy -# environment: -# name: ${{ github.ref_name }} -# steps: -# - name: Checkout -# uses: actions/checkout@v3 - -# - uses: ./.github/actions/setup - -# - name: Configure AWS credentials -# uses: aws-actions/configure-aws-credentials@v2 -# with: -# role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} -# aws-region: us-east-1 -# role-duration-seconds: 10800 - -# - name: Test -# run: yarn test-ci -# e2e: -# timeout-minutes: 5 -# runs-on: ubuntu-20.04 -# needs: -# - deploy -# environment: -# baseurl: HERE -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# - name: Setup -# uses: ./.github/actions/setup -# - name: Install Playwright Browsers -# run: yarn playwright install --with-deps -# - name: Run e2e tests -# run: run e2e -# - uses: actions/upload-artifact@v3 -# if: always() -# with: -# name: playwright-report -# path: playwright-report/ -# retention-days: 30 - -# cfn-nag: -# runs-on: ubuntu-20.04 -# needs: -# - deploy -# environment: -# name: ${{ github.ref_name }} -# steps: -# - name: Checkout -# uses: actions/checkout@v3 - -# - uses: ./.github/actions/setup - -# - name: Configure AWS credentials -# uses: aws-actions/configure-aws-credentials@v2 -# with: -# role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} -# aws-region: us-east-1 -# role-duration-seconds: 10800 - -# - name: Get CloudFormation templates -# id: getCfts -# run: | -# mkdir -p cftemplates -# stackList=(`aws cloudformation describe-stacks --query "Stacks[?Tags[?Key=='STAGE' && Value=='$STAGE_NAME'] && Tags[?Key=='PROJECT' && Value=='$PROJECT']].StackName" --output text`) -# for stack in "${stackList[@]}"; do -# aws cloudformation get-template --stack-name "$stack" --query TemplateBody > "cftemplates/${stack}.json" -# done - -# - name: Stelligent cfn_nag -# uses: stelligent/cfn_nag@v0.8.6 -# with: -# input_path: cftemplates - -# release: -# runs-on: ubuntu-20.04 -# needs: -# - test -# - cfn-nag -# steps: -# - name: Checkout -# uses: actions/checkout@v3 - -# - uses: ./.github/actions/setup - -# - name: Release -# run: npx semantic-release -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -# notify_of_failure: -# runs-on: ubuntu-20.04 -# needs: -# - release -# if: failure() -# steps: -# - name: Slack Notification -# uses: rtCamp/action-slack-notify@v2 -# if: env.SLACK_WEBHOOK != '' && contains(fromJson('["master", "val", "production"]'), env.STAGE_NAME) -# env: -# SLACK_COLOR: ${{job.status}} -# SLACK_ICON: https://github.com/Enterprise-CMCS.png?size=48 -# SLACK_TITLE: Failure -# SLACK_USERNAME: ${{ github.repository }} - ${{job.status}} -# SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - name: Deploy on: From c2911adcb362e15f62ac0b6d5806ced1fe5b8691 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 18 May 2023 16:39:18 -0600 Subject: [PATCH 27/54] test --- .github/workflows/deploy.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a2d9e02813..c3e87526c7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,6 +32,9 @@ jobs: runs-on: ubuntu-20.04 needs: - init + environment: + name: ${{ github.ref_name }} + url: ${{ steps.deployment-data.outputs.APPURL }} outputs: app-url: ${{ steps.deployment-data.outputs.APPURL }} steps: @@ -60,10 +63,10 @@ jobs: --stack-name $PROJECT-ui-infra-$STAGE_NAME \ --query Stacks[0].Outputs[0].OutputValue )" | tr -d \'\" >> $GITHUB_OUTPUT - - name: Export App URL - run: echo "APPURL=${{ steps.deployment-data.outputs.APPURL }}" >> $GITHUB_ENV - - name: Display URL - run: echo $APPURL + # - name: Export App URL + # run: echo "APPURL=${{ steps.deployment-data.outputs.APPURL }}" >> $GITHUB_ENV + # - name: Display URL + # run: echo $APPURL test: runs-on: ubuntu-20.04 From cf880ec0477fa08cab30c6a3ab590da5ae2bbee9 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 22 May 2023 08:56:06 -0600 Subject: [PATCH 28/54] cleanup --- .github/workflows/deploy.yml | 4 --- .../ui/.github/workflows/playwright.yml | 27 ------------------- 2 files changed, 31 deletions(-) delete mode 100644 src/services/ui/.github/workflows/playwright.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c3e87526c7..48e201f4de 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -63,10 +63,6 @@ jobs: --stack-name $PROJECT-ui-infra-$STAGE_NAME \ --query Stacks[0].Outputs[0].OutputValue )" | tr -d \'\" >> $GITHUB_OUTPUT - # - name: Export App URL - # run: echo "APPURL=${{ steps.deployment-data.outputs.APPURL }}" >> $GITHUB_ENV - # - name: Display URL - # run: echo $APPURL test: runs-on: ubuntu-20.04 diff --git a/src/services/ui/.github/workflows/playwright.yml b/src/services/ui/.github/workflows/playwright.yml deleted file mode 100644 index 29a2452a7d..0000000000 --- a/src/services/ui/.github/workflows/playwright.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Playwright Tests -on: - push: - branches: [ play ] - pull_request: - branches: [ play ] -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Install dependencies - run: yarn - - name: Install Playwright Browsers - run: yarn playwright install --with-deps - - name: Run Playwright tests - run: yarn playwright test - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 From 48176c349fcb2e6ad758807c9c042405acf5eb31 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 22 May 2023 10:44:54 -0600 Subject: [PATCH 29/54] Add placeholder api test --- src/services/api/.gitignore | 4 ++ src/services/api/e2e/example.spec.ts | 6 +++ src/services/api/package.json | 10 +++-- src/services/api/playwright.config.ts | 54 +++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/services/api/.gitignore create mode 100644 src/services/api/e2e/example.spec.ts create mode 100644 src/services/api/playwright.config.ts diff --git a/src/services/api/.gitignore b/src/services/api/.gitignore new file mode 100644 index 0000000000..75e854d8dc --- /dev/null +++ b/src/services/api/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/src/services/api/e2e/example.spec.ts b/src/services/api/e2e/example.spec.ts new file mode 100644 index 0000000000..129168487f --- /dev/null +++ b/src/services/api/e2e/example.spec.ts @@ -0,0 +1,6 @@ +import { test, expect } from "@playwright/test"; + +test("get the issues", async ({ request, baseURL }) => { + const issues = await request.get(baseURL + "/issues"); + expect(issues.ok()).toBeTruthy(); +}); diff --git a/src/services/api/package.json b/src/services/api/package.json index 1372c93aa9..81e2d4df0b 100644 --- a/src/services/api/package.json +++ b/src/services/api/package.json @@ -2,21 +2,23 @@ "name": "api", "private": true, "devDependencies": { + "@playwright/test": "^1.33.0", "@types/aws-lambda": "^8.10.111", - "aws-lambda": "^1.0.7", "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.59.0", + "aws-lambda": "^1.0.7", "eslint": "^8.38.0", "eslint-config-custom-server": "*" }, "dependencies": { "@aws-sdk/client-dynamodb": "^3.276.0", + "libs": "*", "shared-types": "*", - "zod": "^3.21.4", - "libs": "*" + "zod": "^3.21.4" }, "version": "0.0.0", "scripts": { - "lint": "eslint '**/*.{ts,js}'" + "lint": "eslint '**/*.{ts,js}'", + "e2e": "playwright test" } } diff --git a/src/services/api/playwright.config.ts b/src/services/api/playwright.config.ts new file mode 100644 index 0000000000..6c2eec8522 --- /dev/null +++ b/src/services/api/playwright.config.ts @@ -0,0 +1,54 @@ +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: "./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: "html", + /* 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: "https://u0lqkcipn2.execute-api.us-east-1.amazonaws.com/play", + extraHTTPHeaders: { + // We set this header per GitHub guidelines. + Accept: "application/vnd.github.v3+json", + // Add authorization token to all requests. + // Assuming personal access token available in the environment. + // 'Authorization': `token ${process.env.API_TOKEN}`, + }, + + /* 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"] }, + }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); From 24d9a113beb90d77b4e0281b46a27eb17e110751 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 22 May 2023 13:49:58 -0600 Subject: [PATCH 30/54] add api sls script --- src/cli/run.ts | 12 ++++++++++-- src/services/api/e2e/example.spec.ts | 1 + src/services/api/package.json | 1 - src/services/api/playwright.config.ts | 4 +++- src/services/api/serverless.yml | 10 ++++++++++ src/services/ui/playwright.config.ts | 2 +- src/services/ui/vite.config.ts | 3 +++ 7 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/cli/run.ts b/src/cli/run.ts index 21986afb3f..ca0502c807 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -98,9 +98,17 @@ yargs(process.argv.slice(2)) .command( "e2e", "run e2e tests.", - {}, - async () => { + { + stage: { type: "string", demandOption: true }, + }, + async (options) => { await install_deps_for_services(); + await runner.run_command_and_output( + `Export api url`, + ["sls", "api", "seturl", "--stage", options.stage], + "." + ); + // get output from api stack for this stage and export env var as baseurl await runner.run_command_and_output(`e2e tests`, ["yarn", "e2e"], "."); } ) diff --git a/src/services/api/e2e/example.spec.ts b/src/services/api/e2e/example.spec.ts index 129168487f..ffc43c5a25 100644 --- a/src/services/api/e2e/example.spec.ts +++ b/src/services/api/e2e/example.spec.ts @@ -1,6 +1,7 @@ import { test, expect } from "@playwright/test"; test("get the issues", async ({ request, baseURL }) => { + console.log({ baseURL }); const issues = await request.get(baseURL + "/issues"); expect(issues.ok()).toBeTruthy(); }); diff --git a/src/services/api/package.json b/src/services/api/package.json index 81e2d4df0b..447295c5e4 100644 --- a/src/services/api/package.json +++ b/src/services/api/package.json @@ -2,7 +2,6 @@ "name": "api", "private": true, "devDependencies": { - "@playwright/test": "^1.33.0", "@types/aws-lambda": "^8.10.111", "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.59.0", diff --git a/src/services/api/playwright.config.ts b/src/services/api/playwright.config.ts index 6c2eec8522..d0afa0b75b 100644 --- a/src/services/api/playwright.config.ts +++ b/src/services/api/playwright.config.ts @@ -24,7 +24,9 @@ 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: "https://u0lqkcipn2.execute-api.us-east-1.amazonaws.com/play", + baseURL: + process.env.API_URL || + "https://u0lqkcipn2.execute-api.us-east-1.amazonaws.com/play", extraHTTPHeaders: { // We set this header per GitHub guidelines. Accept: "application/vnd.github.v3+json", diff --git a/src/services/api/serverless.yml b/src/services/api/serverless.yml index c8b1a2fd64..abacd18894 100644 --- a/src/services/api/serverless.yml +++ b/src/services/api/serverless.yml @@ -7,6 +7,7 @@ plugins: - serverless-stack-termination-protection - "@stratiformdigital/serverless-iam-helper" - "@stratiformdigital/serverless-s3-security-helper" + - serverless-plugin-scripts provider: name: aws runtime: nodejs18.x @@ -46,6 +47,15 @@ custom: - master - val - production + scripts: + commands: + seturl: | + set -e + api_url=$(aws cloudformation describe-stacks --stack-name om-api-play --query 'Stacks[0].Outputs[?OutputKey==`ApiGatewayRestApiUrl`].OutputValue' --output text) + echo $api_url + export API_URL=$api_url + + functions: getIssues: handler: handlers/getIssues.handler diff --git a/src/services/ui/playwright.config.ts b/src/services/ui/playwright.config.ts index ceed25a642..116f791bca 100644 --- a/src/services/ui/playwright.config.ts +++ b/src/services/ui/playwright.config.ts @@ -24,7 +24,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.baseurl || "http://localhost:5174", + baseURL: process.env.baseurl || "http://localhost:5000", /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", diff --git a/src/services/ui/vite.config.ts b/src/services/ui/vite.config.ts index 5fc76f2672..495149d979 100644 --- a/src/services/ui/vite.config.ts +++ b/src/services/ui/vite.config.ts @@ -4,6 +4,9 @@ import react from "@vitejs/plugin-react-swc"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], + server: { + port: 5000, + }, test: { environment: "jsdom", setupFiles: "./testing/setup.ts", From 71a02c44b277339dbc249a580a21848662a338e3 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 22 May 2023 14:21:13 -0600 Subject: [PATCH 31/54] add stage name param --- .github/workflows/deploy.yml | 2 +- src/services/api/serverless.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 48e201f4de..03ec79a0a1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -101,7 +101,7 @@ jobs: - name: Install Playwright Browsers run: yarn playwright install --with-deps - name: Run e2e tests - run: run e2e + run: run e2e --stage $STAGE_NAME - uses: actions/upload-artifact@v3 if: always() with: diff --git a/src/services/api/serverless.yml b/src/services/api/serverless.yml index abacd18894..ac48558603 100644 --- a/src/services/api/serverless.yml +++ b/src/services/api/serverless.yml @@ -51,7 +51,7 @@ custom: commands: seturl: | set -e - api_url=$(aws cloudformation describe-stacks --stack-name om-api-play --query 'Stacks[0].Outputs[?OutputKey==`ApiGatewayRestApiUrl`].OutputValue' --output text) + api_url=$(aws cloudformation describe-stacks --stack-name om-api-${self:custom.stage} --query 'Stacks[0].Outputs[?OutputKey==`ApiGatewayRestApiUrl`].OutputValue' --output text) echo $api_url export API_URL=$api_url From 2ab35632fbc529761a85624fbe5afc64ace93b84 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 22 May 2023 15:38:44 -0600 Subject: [PATCH 32/54] refresh outputs --- src/cli/run.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/run.ts b/src/cli/run.ts index ca0502c807..d72ac790c7 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -103,6 +103,7 @@ yargs(process.argv.slice(2)) }, async (options) => { await install_deps_for_services(); + await refreshOutputs(options.stage); await runner.run_command_and_output( `Export api url`, ["sls", "api", "seturl", "--stage", options.stage], From b7ef03fbfecff9c4526f154b46f4fefcef6eba18 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 22 May 2023 17:09:18 -0600 Subject: [PATCH 33/54] add creds to e2e step --- .github/workflows/deploy.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 03ec79a0a1..0d42097cac 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -98,6 +98,12 @@ jobs: uses: actions/checkout@v3 - name: Setup uses: ./.github/actions/setup + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} + aws-region: us-east-1 + role-duration-seconds: 10800 - name: Install Playwright Browsers run: yarn playwright install --with-deps - name: Run e2e tests From 71e7340b82c31fb6fa8fb906a90e17c03740f719 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 22 May 2023 17:30:49 -0600 Subject: [PATCH 34/54] cleanup --- .github/workflows/deploy.yml | 1 - src/cli/run.ts | 3 +-- src/services/api/e2e/example.spec.ts | 1 - src/services/api/playwright.config.ts | 18 +----------------- src/services/ui/playwright.config.ts | 13 ------------- 5 files changed, 2 insertions(+), 34 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0d42097cac..229c282c9f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -63,7 +63,6 @@ jobs: --stack-name $PROJECT-ui-infra-$STAGE_NAME \ --query Stacks[0].Outputs[0].OutputValue )" | tr -d \'\" >> $GITHUB_OUTPUT - test: runs-on: ubuntu-20.04 needs: diff --git a/src/cli/run.ts b/src/cli/run.ts index d72ac790c7..4f32a6d921 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -105,11 +105,10 @@ yargs(process.argv.slice(2)) await install_deps_for_services(); await refreshOutputs(options.stage); await runner.run_command_and_output( - `Export api url`, + `Export API URL`, ["sls", "api", "seturl", "--stage", options.stage], "." ); - // get output from api stack for this stage and export env var as baseurl await runner.run_command_and_output(`e2e tests`, ["yarn", "e2e"], "."); } ) diff --git a/src/services/api/e2e/example.spec.ts b/src/services/api/e2e/example.spec.ts index ffc43c5a25..129168487f 100644 --- a/src/services/api/e2e/example.spec.ts +++ b/src/services/api/e2e/example.spec.ts @@ -1,7 +1,6 @@ import { test, expect } from "@playwright/test"; test("get the issues", async ({ request, baseURL }) => { - console.log({ baseURL }); const issues = await request.get(baseURL + "/issues"); expect(issues.ok()).toBeTruthy(); }); diff --git a/src/services/api/playwright.config.ts b/src/services/api/playwright.config.ts index d0afa0b75b..6e077026bc 100644 --- a/src/services/api/playwright.config.ts +++ b/src/services/api/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. */ @@ -23,16 +17,13 @@ export default defineConfig({ reporter: "html", /* 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('/')`. */ + // currently this isnt working as intended. we need to allow the envar to be defined here baseURL: process.env.API_URL || "https://u0lqkcipn2.execute-api.us-east-1.amazonaws.com/play", extraHTTPHeaders: { // We set this header per GitHub guidelines. Accept: "application/vnd.github.v3+json", - // Add authorization token to all requests. - // Assuming personal access token available in the environment. - // 'Authorization': `token ${process.env.API_TOKEN}`, }, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ @@ -46,11 +37,4 @@ export default defineConfig({ use: { ...devices["Desktop Chrome"] }, }, ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, }); diff --git a/src/services/ui/playwright.config.ts b/src/services/ui/playwright.config.ts index 116f791bca..cfd8498a5f 100644 --- a/src/services/ui/playwright.config.ts +++ b/src/services/ui/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. */ @@ -47,11 +41,4 @@ export default defineConfig({ use: { ...devices["Desktop Safari"] }, }, ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, }); From 051e8095bafe77a20e65f71219dfd13785d3db8e Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 23 May 2023 14:09:33 -0600 Subject: [PATCH 35/54] add issues test --- .../{example.spec.ts => home/index.spec.ts} | 0 src/services/ui/e2e/issues/issues.spec.ts | 32 +++++++++++++++++++ src/services/ui/package.json | 1 + 3 files changed, 33 insertions(+) rename src/services/ui/e2e/{example.spec.ts => home/index.spec.ts} (100%) create mode 100644 src/services/ui/e2e/issues/issues.spec.ts diff --git a/src/services/ui/e2e/example.spec.ts b/src/services/ui/e2e/home/index.spec.ts similarity index 100% rename from src/services/ui/e2e/example.spec.ts rename to src/services/ui/e2e/home/index.spec.ts diff --git a/src/services/ui/e2e/issues/issues.spec.ts b/src/services/ui/e2e/issues/issues.spec.ts new file mode 100644 index 0000000000..d700871e7b --- /dev/null +++ b/src/services/ui/e2e/issues/issues.spec.ts @@ -0,0 +1,32 @@ +import { test, expect } from "@playwright/test"; +import { v4 as uuidv4 } from "uuid"; + +async function goToIssuesPage(page) { + await page.goto("/"); + await page.getByRole("button", { name: "Issues" }).click(); + await page.getByRole("link", { name: "All Issues" }).click(); + await page.getByRole("button", { name: "Add button" }).click(); +} + +test("create issue should require description", async ({ page }) => { + goToIssuesPage(page); + await page.getByLabel("Title").fill("Here is a test title"); + await page.getByRole("combobox", { name: "Priority" }).selectOption("medium"); + await page.getByRole("combobox", { name: "Type" }).selectOption("other"); + await page.getByRole("button", { name: "Submit button" }).click(); + await expect(page.getByText("Description is required")).toBeVisible(); +}); + +test("should be able to create an issue", async ({ page }) => { + const testDesc = uuidv4(); + goToIssuesPage(page); + + await page.getByLabel("Title").fill("Here is a test title"); + await page.getByLabel("Description").fill(testDesc); + await page.getByRole("combobox", { name: "Priority" }).selectOption("medium"); + await page.getByRole("combobox", { name: "Type" }).selectOption("other"); + await page.getByRole("button", { name: "Submit button" }).click(); + + await expect(page).toHaveURL(/.*issues/); + await expect(page.getByRole("cell", { name: testDesc })).toBeVisible(); +}); diff --git a/src/services/ui/package.json b/src/services/ui/package.json index be3b4a9a96..b9953e776c 100644 --- a/src/services/ui/package.json +++ b/src/services/ui/package.json @@ -27,6 +27,7 @@ "react-hook-form": "^7.43.9", "react-loader-spinner": "^5.3.4", "react-router-dom": "^6.10.0", + "uuid": "^9.0.0", "zod": "^3.21.4" }, "devDependencies": { From 11cb0f1e3c17b59d0514e54842d9f94596cf737a Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 23 May 2023 15:07:05 -0600 Subject: [PATCH 36/54] add crud issue testing --- src/services/ui/e2e/issues/issues.spec.ts | 14 +++++++++++++- src/services/ui/playwright.config.ts | 16 ++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/services/ui/e2e/issues/issues.spec.ts b/src/services/ui/e2e/issues/issues.spec.ts index d700871e7b..05509e946a 100644 --- a/src/services/ui/e2e/issues/issues.spec.ts +++ b/src/services/ui/e2e/issues/issues.spec.ts @@ -17,7 +17,7 @@ test("create issue should require description", async ({ page }) => { await expect(page.getByText("Description is required")).toBeVisible(); }); -test("should be able to create an issue", async ({ page }) => { +test("should be able to create and delete an issue", async ({ page }) => { const testDesc = uuidv4(); goToIssuesPage(page); @@ -29,4 +29,16 @@ test("should be able to create an issue", async ({ page }) => { await expect(page).toHaveURL(/.*issues/); await expect(page.getByRole("cell", { name: testDesc })).toBeVisible(); + + // this next bit is tricky because we need to find the row containing the text we just entered and then find the delete button in the same row. + // but we can do it using xpath selectors + + // Select the "Delete" button using a single XPath selector + const buttonSelector = `//td[text()='${testDesc}']/following-sibling::td/button[@aria-label='Delete button']`; + + // Click the "Delete" button + await page.click(buttonSelector); + + // the row should be deleted + await expect(page.getByRole("cell", { name: testDesc })).not.toBeVisible(); }); diff --git a/src/services/ui/playwright.config.ts b/src/services/ui/playwright.config.ts index cfd8498a5f..b1bc6b3cf4 100644 --- a/src/services/ui/playwright.config.ts +++ b/src/services/ui/playwright.config.ts @@ -31,14 +31,14 @@ export default defineConfig({ use: { ...devices["Desktop Chrome"] }, }, - { - name: "firefox", - use: { ...devices["Desktop Firefox"] }, - }, + // { + // name: "firefox", + // use: { ...devices["Desktop Firefox"] }, + // }, - { - name: "webkit", - use: { ...devices["Desktop Safari"] }, - }, + // { + // name: "webkit", + // use: { ...devices["Desktop Safari"] }, + // }, ], }); From be553c31e31ebc385d7ef318a4c19c2fb2be8e62 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 25 May 2023 13:15:43 -0600 Subject: [PATCH 37/54] remove api testing --- src/cli/run.ts | 8 +-- src/services/api/e2e/example.spec.ts | 6 --- src/services/api/package.json | 3 +- src/services/api/playwright.config.ts | 40 -------------- src/services/api/serverless.yml | 11 +--- src/services/ui/e2e/issues/issues.spec.ts | 5 ++ src/services/ui/src/pages/issue/list.tsx | 66 +++++++++++++---------- 7 files changed, 46 insertions(+), 93 deletions(-) delete mode 100644 src/services/api/e2e/example.spec.ts delete mode 100644 src/services/api/playwright.config.ts diff --git a/src/cli/run.ts b/src/cli/run.ts index 4f32a6d921..d5d3bce2b8 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -101,14 +101,8 @@ yargs(process.argv.slice(2)) { stage: { type: "string", demandOption: true }, }, - async (options) => { + async () => { await install_deps_for_services(); - await refreshOutputs(options.stage); - await runner.run_command_and_output( - `Export API URL`, - ["sls", "api", "seturl", "--stage", options.stage], - "." - ); await runner.run_command_and_output(`e2e tests`, ["yarn", "e2e"], "."); } ) diff --git a/src/services/api/e2e/example.spec.ts b/src/services/api/e2e/example.spec.ts deleted file mode 100644 index 129168487f..0000000000 --- a/src/services/api/e2e/example.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { test, expect } from "@playwright/test"; - -test("get the issues", async ({ request, baseURL }) => { - const issues = await request.get(baseURL + "/issues"); - expect(issues.ok()).toBeTruthy(); -}); diff --git a/src/services/api/package.json b/src/services/api/package.json index 447295c5e4..cfed2e629f 100644 --- a/src/services/api/package.json +++ b/src/services/api/package.json @@ -17,7 +17,6 @@ }, "version": "0.0.0", "scripts": { - "lint": "eslint '**/*.{ts,js}'", - "e2e": "playwright test" + "lint": "eslint '**/*.{ts,js}'" } } diff --git a/src/services/api/playwright.config.ts b/src/services/api/playwright.config.ts deleted file mode 100644 index 6e077026bc..0000000000 --- a/src/services/api/playwright.config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { defineConfig, devices } from "@playwright/test"; - -/** - * See https://playwright.dev/docs/test-configuration. - */ -export default defineConfig({ - 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: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - // currently this isnt working as intended. we need to allow the envar to be defined here - baseURL: - process.env.API_URL || - "https://u0lqkcipn2.execute-api.us-east-1.amazonaws.com/play", - extraHTTPHeaders: { - // We set this header per GitHub guidelines. - Accept: "application/vnd.github.v3+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/src/services/api/serverless.yml b/src/services/api/serverless.yml index ac48558603..8e770a053c 100644 --- a/src/services/api/serverless.yml +++ b/src/services/api/serverless.yml @@ -7,7 +7,6 @@ plugins: - serverless-stack-termination-protection - "@stratiformdigital/serverless-iam-helper" - "@stratiformdigital/serverless-s3-security-helper" - - serverless-plugin-scripts provider: name: aws runtime: nodejs18.x @@ -47,15 +46,7 @@ custom: - master - val - production - scripts: - commands: - seturl: | - set -e - api_url=$(aws cloudformation describe-stacks --stack-name om-api-${self:custom.stage} --query 'Stacks[0].Outputs[?OutputKey==`ApiGatewayRestApiUrl`].OutputValue' --output text) - echo $api_url - export API_URL=$api_url - - + functions: getIssues: handler: handlers/getIssues.handler diff --git a/src/services/ui/e2e/issues/issues.spec.ts b/src/services/ui/e2e/issues/issues.spec.ts index 05509e946a..0d2ea5773f 100644 --- a/src/services/ui/e2e/issues/issues.spec.ts +++ b/src/services/ui/e2e/issues/issues.spec.ts @@ -28,6 +28,11 @@ test("should be able to create and delete an issue", async ({ page }) => { await page.getByRole("button", { name: "Submit button" }).click(); await expect(page).toHaveURL(/.*issues/); + + // you can select react components by name and prop + await expect( + page.locator(`_react=IssueRow[issue.description = "${testDesc}"]`) + ).toBeVisible(); await expect(page.getByRole("cell", { name: testDesc })).toBeVisible(); // this next bit is tricky because we need to find the row containing the text we just entered and then find the delete button in the same row. diff --git a/src/services/ui/src/pages/issue/list.tsx b/src/services/ui/src/pages/issue/list.tsx index 522412e7ff..7209a372d1 100644 --- a/src/services/ui/src/pages/issue/list.tsx +++ b/src/services/ui/src/pages/issue/list.tsx @@ -6,6 +6,39 @@ import * as UI from "@enterprise-cmcs/macpro-ux-lib"; import { useDeleteIssue } from "../../api/useDeleteIssue"; import { Modal } from "../../components/Modal"; import { AddIssueForm, LoadingSpinner } from "../../components"; +import { GetIssue } from "shared-types"; + +export const IssueRow = ({ + issue, + handleDelete, +}: { + issue: GetIssue; + handleDelete: (id: string) => Promise; +}) => ( + + + + {issue.title} + + + {issue.description} + {issue.priority} + {issue.type} + {formatDistance(new Date(issue.createdAt), new Date())} ago + +
+ {issue.resolved ? : } +
+
+ + handleDelete(issue.id)} + /> + + +); export const IssueList = () => { const { isLoading, isError, data } = useGetIssues(); @@ -54,34 +87,11 @@ export const IssueList = () => { {data.map((issue) => { return ( - - - - {issue.title} - - - {issue.description} - {issue.priority} - {issue.type} - - {formatDistance(new Date(issue.createdAt), new Date())} ago - - -
- {issue.resolved ? : } -
-
- - handleDelete(issue.id)} - /> - - + ); })} From dff5bb399c5aea497016dfe28c77577fcc9f97ff Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Fri, 26 May 2023 12:52:49 -0600 Subject: [PATCH 38/54] add and use e2e selectors dir --- src/cli/run.ts | 4 +--- src/services/ui/e2e/selectors/navigation/index.ts | 4 ++++ src/services/ui/e2e/{ => tests}/home/index.spec.ts | 6 ++++-- src/services/ui/e2e/{ => tests}/issues/issues.spec.ts | 0 src/services/ui/e2e/utils/.gitkeep | 0 src/services/ui/src/components/MainWrapper/index.tsx | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 src/services/ui/e2e/selectors/navigation/index.ts rename src/services/ui/e2e/{ => tests}/home/index.spec.ts (71%) rename src/services/ui/e2e/{ => tests}/issues/issues.spec.ts (100%) create mode 100644 src/services/ui/e2e/utils/.gitkeep diff --git a/src/cli/run.ts b/src/cli/run.ts index d5d3bce2b8..21986afb3f 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -98,9 +98,7 @@ yargs(process.argv.slice(2)) .command( "e2e", "run e2e tests.", - { - stage: { type: "string", demandOption: true }, - }, + {}, async () => { await install_deps_for_services(); await runner.run_command_and_output(`e2e tests`, ["yarn", "e2e"], "."); diff --git a/src/services/ui/e2e/selectors/navigation/index.ts b/src/services/ui/e2e/selectors/navigation/index.ts new file mode 100644 index 0000000000..1057641bd0 --- /dev/null +++ b/src/services/ui/e2e/selectors/navigation/index.ts @@ -0,0 +1,4 @@ +export const nav = { + issuesDropDown: "_react=NavSection[section.buttonText = \"Issues\"]", + allIssuesLink: "_react=Link[text = \"All Issues\"]", +}; diff --git a/src/services/ui/e2e/home/index.spec.ts b/src/services/ui/e2e/tests/home/index.spec.ts similarity index 71% rename from src/services/ui/e2e/home/index.spec.ts rename to src/services/ui/e2e/tests/home/index.spec.ts index 884f00d829..415a44a642 100644 --- a/src/services/ui/e2e/home/index.spec.ts +++ b/src/services/ui/e2e/tests/home/index.spec.ts @@ -1,5 +1,7 @@ import { test, expect } from "@playwright/test"; +import { nav } from "../../selectors/navigation/index.ts"; + test("has title", async ({ page }) => { await page.goto("/"); @@ -11,8 +13,8 @@ test("get issues link", async ({ page }) => { await page.goto("/"); // Click the issues link. - await page.getByRole("button", { name: "Issues" }).click(); - await page.getByRole("link", { name: "All Issues" }).click(); + await page.locator(nav.issuesDropDown).click(); + await page.locator(nav.allIssuesLink).click(); // Expects the URL to contain intro. await expect(page).toHaveURL(/.*issues/); diff --git a/src/services/ui/e2e/issues/issues.spec.ts b/src/services/ui/e2e/tests/issues/issues.spec.ts similarity index 100% rename from src/services/ui/e2e/issues/issues.spec.ts rename to src/services/ui/e2e/tests/issues/issues.spec.ts diff --git a/src/services/ui/e2e/utils/.gitkeep b/src/services/ui/e2e/utils/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/services/ui/src/components/MainWrapper/index.tsx b/src/services/ui/src/components/MainWrapper/index.tsx index bba002424b..298496bdfb 100644 --- a/src/services/ui/src/components/MainWrapper/index.tsx +++ b/src/services/ui/src/components/MainWrapper/index.tsx @@ -2,7 +2,7 @@ import * as UI from "@enterprise-cmcs/macpro-ux-lib"; import cmsLogo from "@enterprise-cmcs/macpro-ux-lib/build/assets/img/logos/cms_logo.svg"; import { Outlet } from "react-router-dom"; -export default function MainWrapper({ children }: React.PropsWithChildren) { +export default function MainWrapper() { return (
Date: Tue, 30 May 2023 12:38:18 -0600 Subject: [PATCH 39/54] update pw add selectors and aliases --- src/services/ui/e2e/selectors/index.ts | 1 + src/services/ui/e2e/tests/home/index.spec.ts | 7 +++---- src/services/ui/package.json | 2 +- src/services/ui/tsconfig.json | 8 ++++++-- yarn.lock | 18 +++++++++--------- 5 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 src/services/ui/e2e/selectors/index.ts diff --git a/src/services/ui/e2e/selectors/index.ts b/src/services/ui/e2e/selectors/index.ts new file mode 100644 index 0000000000..cf3f822ed5 --- /dev/null +++ b/src/services/ui/e2e/selectors/index.ts @@ -0,0 +1 @@ +export * from "./navigation/index"; diff --git a/src/services/ui/e2e/tests/home/index.spec.ts b/src/services/ui/e2e/tests/home/index.spec.ts index 415a44a642..3ec79f7e98 100644 --- a/src/services/ui/e2e/tests/home/index.spec.ts +++ b/src/services/ui/e2e/tests/home/index.spec.ts @@ -1,6 +1,5 @@ import { test, expect } from "@playwright/test"; - -import { nav } from "../../selectors/navigation/index.ts"; +import * as $ from "@/selectors"; test("has title", async ({ page }) => { await page.goto("/"); @@ -13,8 +12,8 @@ test("get issues link", async ({ page }) => { await page.goto("/"); // Click the issues link. - await page.locator(nav.issuesDropDown).click(); - await page.locator(nav.allIssuesLink).click(); + await page.locator($.nav.issuesDropDown).click(); + await page.locator($.nav.allIssuesLink).click(); // Expects the URL to contain intro. await expect(page).toHaveURL(/.*issues/); diff --git a/src/services/ui/package.json b/src/services/ui/package.json index b9953e776c..9d05c57706 100644 --- a/src/services/ui/package.json +++ b/src/services/ui/package.json @@ -31,7 +31,7 @@ "zod": "^3.21.4" }, "devDependencies": { - "@playwright/test": "^1.33.0", + "@playwright/test": "^1.34.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/react": "^18.0.28", diff --git a/src/services/ui/tsconfig.json b/src/services/ui/tsconfig.json index 3d0a51a86e..bec9d28c20 100644 --- a/src/services/ui/tsconfig.json +++ b/src/services/ui/tsconfig.json @@ -14,8 +14,12 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/selectors": ["e2e/selectors/index"] + } }, - "include": ["src"], + "include": ["src", "e2e"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/yarn.lock b/yarn.lock index ed8b258276..4da0f11f00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3132,13 +3132,13 @@ dependencies: "@octokit/openapi-types" "^16.0.0" -"@playwright/test@^1.33.0": - version "1.33.0" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.33.0.tgz#669ef859efb81b143dfc624eef99d1dd92a81b67" - integrity sha512-YunBa2mE7Hq4CfPkGzQRK916a4tuZoVx/EpLjeWlTVOnD4S2+fdaQZE0LJkbfhN5FTSKNLdcl7MoT5XB37bTkg== +"@playwright/test@^1.34.0": + version "1.34.3" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.34.3.tgz#d9f1ac3f1a09633b5ca5351c50c308bf802bde53" + integrity sha512-zPLef6w9P6T/iT6XDYG3mvGOqOyb6eHaV9XtkunYs0+OzxBtrPAAaHotc0X+PJ00WPPnLfFBTl7mf45Mn8DBmw== dependencies: "@types/node" "*" - playwright-core "1.33.0" + playwright-core "1.34.3" optionalDependencies: fsevents "2.3.2" @@ -11573,10 +11573,10 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright-core@1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.33.0.tgz#269efe29a927cd6d144d05f3c2d2f72bd72447a1" - integrity sha512-aizyPE1Cj62vAECdph1iaMILpT0WUDCq3E6rW6I+dleSbBoGbktvJtzS6VHkZ4DKNEOG9qJpiom/ZxO+S15LAw== +playwright-core@1.34.3: + version "1.34.3" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.34.3.tgz#bc906ea1b26bb66116ce329436ee59ba2e78fe9f" + integrity sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw== posix-character-classes@^0.1.0: version "0.1.1" From b07c1cf2b766dc922219e8a9823ba31c8b0be2b4 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Thu, 1 Jun 2023 09:55:50 -0600 Subject: [PATCH 40/54] add type props to e2e test --- src/services/ui/e2e/tests/issues/issues.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/ui/e2e/tests/issues/issues.spec.ts b/src/services/ui/e2e/tests/issues/issues.spec.ts index 0d2ea5773f..8f7e4c0961 100644 --- a/src/services/ui/e2e/tests/issues/issues.spec.ts +++ b/src/services/ui/e2e/tests/issues/issues.spec.ts @@ -1,7 +1,7 @@ -import { test, expect } from "@playwright/test"; +import { test, expect, Page } from "@playwright/test"; import { v4 as uuidv4 } from "uuid"; -async function goToIssuesPage(page) { +async function goToIssuesPage(page: Page) { await page.goto("/"); await page.getByRole("button", { name: "Issues" }).click(); await page.getByRole("link", { name: "All Issues" }).click(); From 07f3300fba9f71e1f6c6dbc61b9ad23025dbbcfb Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Sun, 4 Jun 2023 20:38:34 -0600 Subject: [PATCH 41/54] update e2e selectors --- .../components/AddIssueForm/index.ts | 12 ++++++ .../ui/e2e/selectors/components/index.ts | 1 + src/services/ui/e2e/selectors/index.ts | 1 + .../ui/e2e/selectors/navigation/index.ts | 8 +++- src/services/ui/e2e/tests/home/index.spec.ts | 4 +- .../ui/e2e/tests/issues/issues.spec.ts | 42 ++++++++++--------- 6 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 src/services/ui/e2e/selectors/components/AddIssueForm/index.ts create mode 100644 src/services/ui/e2e/selectors/components/index.ts diff --git a/src/services/ui/e2e/selectors/components/AddIssueForm/index.ts b/src/services/ui/e2e/selectors/components/AddIssueForm/index.ts new file mode 100644 index 0000000000..ed65ca9920 --- /dev/null +++ b/src/services/ui/e2e/selectors/components/AddIssueForm/index.ts @@ -0,0 +1,12 @@ +import { Page } from "@playwright/test"; + +export const addIssueForm = { + addButton: (page: Page) => page.locator("_react=Button[buttonText = \"Add\"]"), + submitButton: (page: Page) => + page.locator("_react=Button[buttonText = \"Submit\"]"), + titleInput: (page: Page) => page.getByLabel("Title"), + descriptionInput: (page: Page) => page.getByLabel("Description"), + prioritySelect: (page: Page) => + page.getByRole("combobox", { name: "Priority" }), + typeSelect: (page: Page) => page.getByRole("combobox", { name: "Type" }), +}; diff --git a/src/services/ui/e2e/selectors/components/index.ts b/src/services/ui/e2e/selectors/components/index.ts new file mode 100644 index 0000000000..f74efd71cd --- /dev/null +++ b/src/services/ui/e2e/selectors/components/index.ts @@ -0,0 +1 @@ +export * from "./AddIssueForm/index"; diff --git a/src/services/ui/e2e/selectors/index.ts b/src/services/ui/e2e/selectors/index.ts index cf3f822ed5..d9603ff819 100644 --- a/src/services/ui/e2e/selectors/index.ts +++ b/src/services/ui/e2e/selectors/index.ts @@ -1 +1,2 @@ export * from "./navigation/index"; +export * from "./components/index"; diff --git a/src/services/ui/e2e/selectors/navigation/index.ts b/src/services/ui/e2e/selectors/navigation/index.ts index 1057641bd0..39570c6972 100644 --- a/src/services/ui/e2e/selectors/navigation/index.ts +++ b/src/services/ui/e2e/selectors/navigation/index.ts @@ -1,4 +1,8 @@ +import { Page } from "@playwright/test"; + export const nav = { - issuesDropDown: "_react=NavSection[section.buttonText = \"Issues\"]", - allIssuesLink: "_react=Link[text = \"All Issues\"]", + issuesDropDown: (page: Page) => + page.locator("_react=NavSection[section.buttonText = \"Issues\"]"), + allIssuesLink: (page: Page) => + page.locator("_react=Link[text = \"All Issues\"]"), }; diff --git a/src/services/ui/e2e/tests/home/index.spec.ts b/src/services/ui/e2e/tests/home/index.spec.ts index 3ec79f7e98..7d251b3546 100644 --- a/src/services/ui/e2e/tests/home/index.spec.ts +++ b/src/services/ui/e2e/tests/home/index.spec.ts @@ -12,8 +12,8 @@ test("get issues link", async ({ page }) => { await page.goto("/"); // Click the issues link. - await page.locator($.nav.issuesDropDown).click(); - await page.locator($.nav.allIssuesLink).click(); + await $.nav.issuesDropDown(page).click(); + await $.nav.allIssuesLink(page).click(); // Expects the URL to contain intro. await expect(page).toHaveURL(/.*issues/); diff --git a/src/services/ui/e2e/tests/issues/issues.spec.ts b/src/services/ui/e2e/tests/issues/issues.spec.ts index 8f7e4c0961..9ccb452a13 100644 --- a/src/services/ui/e2e/tests/issues/issues.spec.ts +++ b/src/services/ui/e2e/tests/issues/issues.spec.ts @@ -1,43 +1,45 @@ import { test, expect, Page } from "@playwright/test"; import { v4 as uuidv4 } from "uuid"; +import * as $ from "@/selectors"; async function goToIssuesPage(page: Page) { await page.goto("/"); - await page.getByRole("button", { name: "Issues" }).click(); - await page.getByRole("link", { name: "All Issues" }).click(); - await page.getByRole("button", { name: "Add button" }).click(); + + // Click the issues link. + await $.nav.issuesDropDown(page).click(); + await $.nav.allIssuesLink(page).click(); +} + +async function clickToAddIssue(page: Page) { + await $.addIssueForm.addButton(page).click(); } test("create issue should require description", async ({ page }) => { goToIssuesPage(page); - await page.getByLabel("Title").fill("Here is a test title"); - await page.getByRole("combobox", { name: "Priority" }).selectOption("medium"); - await page.getByRole("combobox", { name: "Type" }).selectOption("other"); - await page.getByRole("button", { name: "Submit button" }).click(); + clickToAddIssue(page); + + await $.addIssueForm.titleInput(page).fill("Here is a test title"); + await $.addIssueForm.prioritySelect(page).selectOption("medium"); + await $.addIssueForm.typeSelect(page).selectOption("other"); + await $.addIssueForm.submitButton(page).click(); + await expect(page.getByText("Description is required")).toBeVisible(); }); test("should be able to create and delete an issue", async ({ page }) => { const testDesc = uuidv4(); goToIssuesPage(page); + clickToAddIssue(page); - await page.getByLabel("Title").fill("Here is a test title"); - await page.getByLabel("Description").fill(testDesc); - await page.getByRole("combobox", { name: "Priority" }).selectOption("medium"); - await page.getByRole("combobox", { name: "Type" }).selectOption("other"); - await page.getByRole("button", { name: "Submit button" }).click(); + await $.addIssueForm.titleInput(page).fill("Here is a test title"); + await $.addIssueForm.descriptionInput(page).fill(testDesc); + await $.addIssueForm.prioritySelect(page).selectOption("medium"); + await $.addIssueForm.typeSelect(page).selectOption("other"); + await $.addIssueForm.submitButton(page).click(); await expect(page).toHaveURL(/.*issues/); - - // you can select react components by name and prop - await expect( - page.locator(`_react=IssueRow[issue.description = "${testDesc}"]`) - ).toBeVisible(); await expect(page.getByRole("cell", { name: testDesc })).toBeVisible(); - // this next bit is tricky because we need to find the row containing the text we just entered and then find the delete button in the same row. - // but we can do it using xpath selectors - // Select the "Delete" button using a single XPath selector const buttonSelector = `//td[text()='${testDesc}']/following-sibling::td/button[@aria-label='Delete button']`; From 1cf49cda9736480f81d958f5cc498851abe47dba Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Sun, 4 Jun 2023 20:51:00 -0600 Subject: [PATCH 42/54] remove stage from e2e workflow --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c360f946fc..038810465b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -106,7 +106,7 @@ jobs: - name: Install Playwright Browsers run: yarn playwright install --with-deps - name: Run e2e tests - run: run e2e --stage $STAGE_NAME + run: run e2e - uses: actions/upload-artifact@v3 if: always() with: From 6eb1746f2ed9c0c26317b4b119d1521a233a9b15 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 5 Jun 2023 11:25:36 -0600 Subject: [PATCH 43/54] update selectors --- .../components/AddIssueForm/index.ts | 41 ++++++++++++++----- .../ui/e2e/selectors/components/index.ts | 4 +- .../ui/e2e/selectors/navigation/index.ts | 23 ++++++++--- src/services/ui/e2e/tests/home/index.spec.ts | 5 ++- .../ui/e2e/tests/issues/issues.spec.ts | 34 +++++++-------- 5 files changed, 71 insertions(+), 36 deletions(-) diff --git a/src/services/ui/e2e/selectors/components/AddIssueForm/index.ts b/src/services/ui/e2e/selectors/components/AddIssueForm/index.ts index ed65ca9920..e7feba41f7 100644 --- a/src/services/ui/e2e/selectors/components/AddIssueForm/index.ts +++ b/src/services/ui/e2e/selectors/components/AddIssueForm/index.ts @@ -1,12 +1,33 @@ import { Page } from "@playwright/test"; -export const addIssueForm = { - addButton: (page: Page) => page.locator("_react=Button[buttonText = \"Add\"]"), - submitButton: (page: Page) => - page.locator("_react=Button[buttonText = \"Submit\"]"), - titleInput: (page: Page) => page.getByLabel("Title"), - descriptionInput: (page: Page) => page.getByLabel("Description"), - prioritySelect: (page: Page) => - page.getByRole("combobox", { name: "Priority" }), - typeSelect: (page: Page) => page.getByRole("combobox", { name: "Type" }), -}; +export class AddIssueFormSelectors { + private page: Page; + + constructor(page: Page) { + this.page = page; + } + + get addButton() { + return this.page.locator("_react=Button[buttonText=\"Add\"]"); + } + + get submitButton() { + return this.page.locator("_react=Button[buttonText=\"Submit\"]"); + } + + get titleInput() { + return this.page.getByLabel("Title"); + } + + get descriptionInput() { + return this.page.getByLabel("Description"); + } + + get prioritySelect() { + return this.page.getByRole("combobox", { name: "Priority" }); + } + + get typeSelect() { + return this.page.getByRole("combobox", { name: "Type" }); + } +} diff --git a/src/services/ui/e2e/selectors/components/index.ts b/src/services/ui/e2e/selectors/components/index.ts index f74efd71cd..b27e5616d1 100644 --- a/src/services/ui/e2e/selectors/components/index.ts +++ b/src/services/ui/e2e/selectors/components/index.ts @@ -1 +1,3 @@ -export * from "./AddIssueForm/index"; +import { AddIssueFormSelectors } from "./AddIssueForm/index"; + +export { AddIssueFormSelectors }; diff --git a/src/services/ui/e2e/selectors/navigation/index.ts b/src/services/ui/e2e/selectors/navigation/index.ts index 39570c6972..cb98c48358 100644 --- a/src/services/ui/e2e/selectors/navigation/index.ts +++ b/src/services/ui/e2e/selectors/navigation/index.ts @@ -1,8 +1,19 @@ import { Page } from "@playwright/test"; -export const nav = { - issuesDropDown: (page: Page) => - page.locator("_react=NavSection[section.buttonText = \"Issues\"]"), - allIssuesLink: (page: Page) => - page.locator("_react=Link[text = \"All Issues\"]"), -}; +export class NavSelectors { + private page: Page; + + constructor(page: Page) { + this.page = page; + } + + get issuesDropDown() { + return this.page.locator( + "_react=NavSection[section.buttonText = \"Issues\"]" + ); + } + + get allIssuesLink() { + return this.page.locator("_react=Link[text = \"All Issues\"]"); + } +} diff --git a/src/services/ui/e2e/tests/home/index.spec.ts b/src/services/ui/e2e/tests/home/index.spec.ts index 7d251b3546..4fe7b3772f 100644 --- a/src/services/ui/e2e/tests/home/index.spec.ts +++ b/src/services/ui/e2e/tests/home/index.spec.ts @@ -9,11 +9,12 @@ test("has title", async ({ page }) => { }); test("get issues link", async ({ page }) => { + const navSelectors = new $.NavSelectors(page); await page.goto("/"); // Click the issues link. - await $.nav.issuesDropDown(page).click(); - await $.nav.allIssuesLink(page).click(); + await navSelectors.issuesDropDown.click(); + await navSelectors.allIssuesLink.click(); // Expects the URL to contain intro. await expect(page).toHaveURL(/.*issues/); diff --git a/src/services/ui/e2e/tests/issues/issues.spec.ts b/src/services/ui/e2e/tests/issues/issues.spec.ts index 9ccb452a13..8cdf21ace1 100644 --- a/src/services/ui/e2e/tests/issues/issues.spec.ts +++ b/src/services/ui/e2e/tests/issues/issues.spec.ts @@ -3,39 +3,39 @@ import { v4 as uuidv4 } from "uuid"; import * as $ from "@/selectors"; async function goToIssuesPage(page: Page) { + const navSelectors = new $.NavSelectors(page); await page.goto("/"); // Click the issues link. - await $.nav.issuesDropDown(page).click(); - await $.nav.allIssuesLink(page).click(); -} - -async function clickToAddIssue(page: Page) { - await $.addIssueForm.addButton(page).click(); + await navSelectors.issuesDropDown.click(); + await navSelectors.allIssuesLink.click(); } test("create issue should require description", async ({ page }) => { + const addIssuesFormSelectors = new $.AddIssueFormSelectors(page); goToIssuesPage(page); - clickToAddIssue(page); - await $.addIssueForm.titleInput(page).fill("Here is a test title"); - await $.addIssueForm.prioritySelect(page).selectOption("medium"); - await $.addIssueForm.typeSelect(page).selectOption("other"); - await $.addIssueForm.submitButton(page).click(); + await addIssuesFormSelectors.addButton.click(); + await addIssuesFormSelectors.titleInput.fill("Here is a test title"); + await addIssuesFormSelectors.prioritySelect.selectOption("medium"); + await addIssuesFormSelectors.typeSelect.selectOption("other"); + await addIssuesFormSelectors.submitButton.click(); await expect(page.getByText("Description is required")).toBeVisible(); }); test("should be able to create and delete an issue", async ({ page }) => { + const addIssuesFormSelectors = new $.AddIssueFormSelectors(page); + const testDesc = uuidv4(); goToIssuesPage(page); - clickToAddIssue(page); - await $.addIssueForm.titleInput(page).fill("Here is a test title"); - await $.addIssueForm.descriptionInput(page).fill(testDesc); - await $.addIssueForm.prioritySelect(page).selectOption("medium"); - await $.addIssueForm.typeSelect(page).selectOption("other"); - await $.addIssueForm.submitButton(page).click(); + await addIssuesFormSelectors.addButton.click(); + await addIssuesFormSelectors.titleInput.fill("Here is a test title"); + await addIssuesFormSelectors.descriptionInput.fill(testDesc); + await addIssuesFormSelectors.prioritySelect.selectOption("medium"); + await addIssuesFormSelectors.typeSelect.selectOption("other"); + await addIssuesFormSelectors.submitButton.click(); await expect(page).toHaveURL(/.*issues/); await expect(page.getByRole("cell", { name: testDesc })).toBeVisible(); From e04551068797192c9c4714ca906a4e857c76c020 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 5 Jun 2023 12:27:01 -0600 Subject: [PATCH 44/54] dont minify to test --- src/services/ui/vite.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/services/ui/vite.config.ts b/src/services/ui/vite.config.ts index 495149d979..629d890287 100644 --- a/src/services/ui/vite.config.ts +++ b/src/services/ui/vite.config.ts @@ -16,4 +16,7 @@ export default defineConfig({ }, exclude: ["**/e2e/**"], }, + build: { + minify: false, + }, }); From 3f7f9ab8e5c8b6348841f97ed149df27beae0be8 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 5 Jun 2023 14:36:21 -0600 Subject: [PATCH 45/54] Set environment variable for production --- .github/workflows/deploy.yml | 5 +++++ src/services/ui/vite.config.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 038810465b..8421e7d8a3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -49,6 +49,11 @@ jobs: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} aws-region: us-east-1 role-duration-seconds: 10800 + + - name: Set environment variable for production + env: + NODE_ENV: production + if: github.ref == 'refs/heads/production' - name: Deploy run: run deploy --stage $STAGE_NAME diff --git a/src/services/ui/vite.config.ts b/src/services/ui/vite.config.ts index 629d890287..09af496da4 100644 --- a/src/services/ui/vite.config.ts +++ b/src/services/ui/vite.config.ts @@ -17,6 +17,6 @@ export default defineConfig({ exclude: ["**/e2e/**"], }, build: { - minify: false, + minify: process.env.NODE_ENV === "production", }, }); From 232981b55ebb0f6ae7c91b7a970770cfa597c29c Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 5 Jun 2023 14:54:47 -0600 Subject: [PATCH 46/54] fix workflow formatting --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8421e7d8a3..59645ac140 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -53,7 +53,7 @@ jobs: - name: Set environment variable for production env: NODE_ENV: production - if: github.ref == 'refs/heads/production' + if: ${{ github.ref_name }} == 'production' - name: Deploy run: run deploy --stage $STAGE_NAME From fe26c16355cb9e8d40776d7e0b91ab79df99d14f Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 5 Jun 2023 14:57:55 -0600 Subject: [PATCH 47/54] fix workflow formatting --- .github/workflows/deploy.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 59645ac140..88fc242b7a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -51,9 +51,8 @@ jobs: role-duration-seconds: 10800 - name: Set environment variable for production - env: - NODE_ENV: production - if: ${{ github.ref_name }} == 'production' + run: echo "NODE_ENV=production" >> $GITHUB_ENV + if: github.ref == 'refs/heads/production' - name: Deploy run: run deploy --stage $STAGE_NAME From bb475e7b904ba6131cf18555aa6f687f5d17eafe Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 5 Jun 2023 15:12:49 -0600 Subject: [PATCH 48/54] testing minify change --- src/services/ui/vite.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/ui/vite.config.ts b/src/services/ui/vite.config.ts index 09af496da4..cc2be76c3d 100644 --- a/src/services/ui/vite.config.ts +++ b/src/services/ui/vite.config.ts @@ -17,6 +17,7 @@ export default defineConfig({ exclude: ["**/e2e/**"], }, build: { - minify: process.env.NODE_ENV === "production", + // minify: process.env.NODE_ENV === "production", + minify: false, }, }); From 977ecf4f7084eda26a7bf4374736d04865b7ec01 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 5 Jun 2023 15:18:11 -0600 Subject: [PATCH 49/54] update artifact path --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 88fc242b7a..aa59493094 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -115,7 +115,7 @@ jobs: if: always() with: name: playwright-report - path: playwright-report/ + path: src/services/ui/playwright-report/ retention-days: 30 cfn-nag: From d2b2ca2d5a4177d0ad7cdb4f4522f05f2decbafa Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 6 Jun 2023 14:16:21 -0600 Subject: [PATCH 50/54] update minify logic --- src/services/ui/serverless.yml | 1 + src/services/ui/vite.config.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/services/ui/serverless.yml b/src/services/ui/serverless.yml index 401fe0a9d6..1caf0cb30d 100644 --- a/src/services/ui/serverless.yml +++ b/src/services/ui/serverless.yml @@ -41,5 +41,6 @@ custom: echo """ VITE_API_REGION=${param:ApiRegion} VITE_API_URL=${param:ApiUrl} + VITE_NODE_ENV=${self:custom.stage} """ > .env.local yarn build diff --git a/src/services/ui/vite.config.ts b/src/services/ui/vite.config.ts index cc2be76c3d..8d3394d5d0 100644 --- a/src/services/ui/vite.config.ts +++ b/src/services/ui/vite.config.ts @@ -17,7 +17,9 @@ export default defineConfig({ exclude: ["**/e2e/**"], }, build: { - // minify: process.env.NODE_ENV === "production", - minify: false, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + minify: import.meta.env.VITE_NODE_ENV === "production", + // minify: false, }, }); From cb1794e390f41f0ee1929219d0b5789459c7f4ff Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 6 Jun 2023 14:23:05 -0600 Subject: [PATCH 51/54] update minify logic --- src/services/ui/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ui/vite.config.ts b/src/services/ui/vite.config.ts index 8d3394d5d0..4b731d550a 100644 --- a/src/services/ui/vite.config.ts +++ b/src/services/ui/vite.config.ts @@ -19,7 +19,7 @@ export default defineConfig({ build: { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - minify: import.meta.env.VITE_NODE_ENV === "production", + minify: process.env.VITE_NODE_ENV === "production", // minify: false, }, }); From 44af9fb0d37300d5ecdb8d49ac18028a30c1c0fb Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 6 Jun 2023 15:20:58 -0600 Subject: [PATCH 52/54] add docs --- .github/workflows/deploy.yml | 7 ++--- docs/docs/developer-guide/e2e-testing.md | 35 ++++++++++++++++++++++++ src/services/ui/vite.config.ts | 3 -- 3 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 docs/docs/developer-guide/e2e-testing.md diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index aa59493094..2cc4f264d7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -49,10 +49,6 @@ jobs: role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }} aws-region: us-east-1 role-duration-seconds: 10800 - - - name: Set environment variable for production - run: echo "NODE_ENV=production" >> $GITHUB_ENV - if: github.ref == 'refs/heads/production' - name: Deploy run: run deploy --stage $STAGE_NAME @@ -96,8 +92,9 @@ jobs: - deploy env: baseurl: ${{ needs.deploy.outputs.app-url }} + if: ${{ github.ref != 'refs/heads/production' }} steps: - - name: Checkout + - name: Checkout uses: actions/checkout@v3 - name: Setup uses: ./.github/actions/setup diff --git a/docs/docs/developer-guide/e2e-testing.md b/docs/docs/developer-guide/e2e-testing.md new file mode 100644 index 0000000000..935ed00cdd --- /dev/null +++ b/docs/docs/developer-guide/e2e-testing.md @@ -0,0 +1,35 @@ +layout: default +title: End-to-end Testing +parent: Developer Guide +nav_order: 11 + +# e2e testing using Playwright +{: .no_toc } + +## Table of Contents +{: .no_toc .text-delta } + +- TOC +{:toc} + +--- + +## What is Playwright +The Playwright testing framework provides a powerful set of tools and APIs for automating and testing web applications. This guide will introduce you to the basics of using the Playwright framework and its core concepts. + +The Playwright framework allows you to write tests that simulate user interactions with web pages, such as clicking buttons, filling forms, and validating page content. It supports multiple web browsers, including Chromium, Firefox, and WebKit, enabling cross-browser testing. + +## How do I run tests +As a developer you run e2e tests against a local running instance of the application. By using the `run ui --stage [stage]` command to deploy and init an instance of the application. A second terminal can be used to run the `run e2e` command which will run the playwright tests against the currently running instance of the ui on localhost:5000. + +## How do I write tests +The e2e tests for the UI service are located in `src/services/ui/e2e` and defined in .spec files. Use the examples found to guide how to structure your tests. + +When defining the selectors for elements make sure to define your selectors in the `e2e/selectors` directory. Defining selectors using react component names and properties is prefered to maintain consistent scalable testing. Other patterns can be used such as xpath, element type/name, and id/data-id/test-id. But should only be used when necessary. + +## Running in the pipeline +An action has been added to the deploy github actions workflow that runs the e2e tests after the application deployment against the deployed frontend url. Note: This action will run in all environments except production. + +## Additional Notes: + +Playwright is very powerful but should be used only to test general purpose user journeys. A test should NOT be create to test every ticket or story. Please review the documentation for additional functionality: [Playwright Docs](https://playwright.dev/docs/intro) \ No newline at end of file diff --git a/src/services/ui/vite.config.ts b/src/services/ui/vite.config.ts index 4b731d550a..cb66407648 100644 --- a/src/services/ui/vite.config.ts +++ b/src/services/ui/vite.config.ts @@ -17,9 +17,6 @@ export default defineConfig({ exclude: ["**/e2e/**"], }, build: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore minify: process.env.VITE_NODE_ENV === "production", - // minify: false, }, }); From 4453b4408830aa1b2059cfe3235812770aa62c36 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 6 Jun 2023 15:51:30 -0600 Subject: [PATCH 53/54] add comments --- src/services/api/.gitignore | 4 ---- src/services/ui/e2e/tests/issues/issues.spec.ts | 8 +++++++- 2 files changed, 7 insertions(+), 5 deletions(-) delete mode 100644 src/services/api/.gitignore diff --git a/src/services/api/.gitignore b/src/services/api/.gitignore deleted file mode 100644 index 75e854d8dc..0000000000 --- a/src/services/api/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -/test-results/ -/playwright-report/ -/playwright/.cache/ diff --git a/src/services/ui/e2e/tests/issues/issues.spec.ts b/src/services/ui/e2e/tests/issues/issues.spec.ts index 8cdf21ace1..bdb09d5deb 100644 --- a/src/services/ui/e2e/tests/issues/issues.spec.ts +++ b/src/services/ui/e2e/tests/issues/issues.spec.ts @@ -15,12 +15,14 @@ test("create issue should require description", async ({ page }) => { const addIssuesFormSelectors = new $.AddIssueFormSelectors(page); goToIssuesPage(page); + // fills in all inputs except description await addIssuesFormSelectors.addButton.click(); await addIssuesFormSelectors.titleInput.fill("Here is a test title"); await addIssuesFormSelectors.prioritySelect.selectOption("medium"); await addIssuesFormSelectors.typeSelect.selectOption("other"); await addIssuesFormSelectors.submitButton.click(); + // expects required error to be displayed await expect(page.getByText("Description is required")).toBeVisible(); }); @@ -30,6 +32,7 @@ test("should be able to create and delete an issue", async ({ page }) => { const testDesc = uuidv4(); goToIssuesPage(page); + // completes and submits new issues form await addIssuesFormSelectors.addButton.click(); await addIssuesFormSelectors.titleInput.fill("Here is a test title"); await addIssuesFormSelectors.descriptionInput.fill(testDesc); @@ -37,7 +40,10 @@ test("should be able to create and delete an issue", async ({ page }) => { await addIssuesFormSelectors.typeSelect.selectOption("other"); await addIssuesFormSelectors.submitButton.click(); + // expect the page to be rerouted await expect(page).toHaveURL(/.*issues/); + + // expect the issue row to exist await expect(page.getByRole("cell", { name: testDesc })).toBeVisible(); // Select the "Delete" button using a single XPath selector @@ -46,6 +52,6 @@ test("should be able to create and delete an issue", async ({ page }) => { // Click the "Delete" button await page.click(buttonSelector); - // the row should be deleted + // expect the row to be deleted await expect(page.getByRole("cell", { name: testDesc })).not.toBeVisible(); }); From dcd6f77def4722d750fe65b49c5603686d052f35 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Wed, 7 Jun 2023 09:44:03 -0600 Subject: [PATCH 54/54] uncomment browsers --- src/services/ui/playwright.config.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/services/ui/playwright.config.ts b/src/services/ui/playwright.config.ts index b1bc6b3cf4..a7a501bca2 100644 --- a/src/services/ui/playwright.config.ts +++ b/src/services/ui/playwright.config.ts @@ -25,20 +25,20 @@ export default defineConfig({ }, /* Configure projects for major browsers */ + // Note: we can test on multiple browsers and resolutions defined here projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] }, }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, - // { - // name: "firefox", - // use: { ...devices["Desktop Firefox"] }, - // }, - - // { - // name: "webkit", - // use: { ...devices["Desktop Safari"] }, - // }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, ], });