diff --git a/Jenkinsfile b/Jenkinsfile index 530eb77..6a2f5fa 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,9 +2,10 @@ pipeline { agent any environment { - PLAYWRIGHT_IMAGE = 'mcr.microsoft.com/playwright:v1.52.0-noble' + PLAYWRIGHT_IMAGE = 'mcr.microsoft.com/playwright:v1.56.0-noble' WORK_DIR = '/app' - REPORT_DIR = 'reports/playwright' + REPORT_DIR = 'reports/' + BASE_URL = 'https://your-jenkins-instance.com' // Replace with your Jenkins base URL } stages { @@ -40,51 +41,47 @@ pipeline { stage('Run Tests') { steps { - def injectCredsIfExists = { - def creds = [:] - creds.URL = '' - creds.USERNAME = '' - creds.PASSWORD = '' + catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { + script { + def testUrl = '' + def testUser = '' + def testPass = '' try { withCredentials([ - string(credentialsId: 'TEST_URL', variable: 'TEST_URL'), - usernamePassword(credentialsId: 'TEST_CREDENTIALS', usernameVariable: 'TEST_USERNAME', passwordVariable: 'TEST_PASSWORD') - ]) { - creds.URL = env.'TEST_URL' - creds.USERNAME = env.'TEST_USERNAME' - creds.PASSWORD = env.'TEST_PASSWORD' - echo "Injected credentials for ${prefix}" - } - } catch (ignored) { - echo "No credentials found for ${prefix}, using default values" + string(credentialsId: 'TEST_URL', variable: 'TEST_URL'), + usernamePassword(credentialsId: 'TEST_CREDENTIALS', usernameVariable: 'TEST_USERNAME', passwordVariable: 'TEST_PASSWORD') + ]) { + testUrl = env.TEST_URL + testUser = env.TEST_USERNAME + testPass = env.TEST_PASSWORD + echo 'Credentials injected' + } + } catch (ignored) { + echo 'No credentials found, using defaults' } - return creds - } - catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { - script { - def creds = injectCredsIfExists() - try { - sh """ - docker run --rm \ - -v "${env.WORKSPACE}:${WORK_DIR}" \ - -w ${WORK_DIR} \ - -e JENKINS=true \ - -e ENVIRONMENT="${params.ENVIRONMENT}" \ - -e PLAYWRIGHT_HTML_REPORT_DIR="${REPORT_DIR}/playwright-report" \ - -e CUSTOM_REPORT_DIR="${REPORT_DIR}" \ - -e ${prefix}_URL="${creds.URL}" \ - -e ${prefix}_USERNAME="${creds.USERNAME}" \ - -e ${prefix}_PASSWORD="${creds.PASSWORD}" \ - ${PLAYWRIGHT_IMAGE} bash -c ' - mkdir -p "${REPORT_DIR}" - npx playwright test --project=chromium - ' - """ - } finally { - archiveArtifacts artifacts: 'test-results/**/*', allowEmptyArchive: true - } + def jobPath = env.JOB_NAME.tokenize('/').collect { "job/${it.replaceAll(' ', '%20')}" }.join('/') + def reportUrl = "${BASE_URL}/${jobPath}/${env.BUILD_NUMBER}" + + sh """ + docker run --rm \ + -v "${env.WORKSPACE}:${WORK_DIR}" \ + -w ${WORK_DIR} \ + -e JENKINS=true \ + -e ENVIRONMENT="${params.ENVIRONMENT}" \ + -e PLAYWRIGHT_HTML_REPORT_DIR="${REPORT_DIR}/playwright-report" \ + -e CUSTOM_REPORT_DIR="${REPORT_DIR}" \ + -e JENKINS_URL="${reportUrl}" \ + -e JENKINS_TEST_RESULTS="${reportUrl}/artifact" \ + -e URL="${testUrl}" \ + -e USERNAME="${testUser}" \ + -e PASSWORD="${testPass}" \ + ${PLAYWRIGHT_IMAGE} bash -c ' + mkdir -p "${REPORT_DIR}" + npx playwright test --project=chromium + ' + """ } } } @@ -93,14 +90,14 @@ pipeline { stage('Publish HTML Report') { steps { publishHTML(target: [ - reportName: 'Playwright Report', - reportDir: "${env.REPORT_DIR}", - reportFiles: 'detailed-report.html', - keepAll: true, - alwaysLinkToLastBuild: true, - allowMissing: false, - escapeHtml: false - ]) + reportName: 'Test Results Dashboard', + reportDir: "${REPORT_DIR}", + reportFiles: 'detailed-report.html', + keepAll: true, + alwaysLinkToLastBuild: true, + allowMissing: false, + escapeHtml: false + ]) } } } diff --git a/package-lock.json b/package-lock.json index 4305be5..65d9563 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@eslint/js": "^9.24.0", - "@playwright/test": "^1.53.1", + "@playwright/test": "^1.56.0", "@types/node": "^24.0.3", "@typescript-eslint/eslint-plugin": "^8.29.1", "@typescript-eslint/parser": "^8.29.1", @@ -708,13 +708,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.53.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz", - "integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz", + "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.53.1" + "playwright": "1.56.0" }, "bin": { "playwright": "cli.js" @@ -3160,13 +3160,13 @@ } }, "node_modules/playwright": { - "version": "1.53.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz", - "integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz", + "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.53.1" + "playwright-core": "1.56.0" }, "bin": { "playwright": "cli.js" @@ -3179,9 +3179,9 @@ } }, "node_modules/playwright-core": { - "version": "1.53.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz", - "integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz", + "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index eb57287..9dd5f98 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "��#\u0000 \u0000p\u0000l\u0000a\u0000y\u0000w\u0000r\u0000i\u0000g\u0000h\u0000t\u0000-\u0000t\u0000y\u0000p\u0000e\u0000s\u0000c\u0000r\u0000i\u0000p\u0000t\u0000-\u0000t\u0000e\u0000m\u0000p\u0000l\u0000a\u0000t\u0000e\u0000\r\u0000 \u0000", "main": "index.js", "scripts": { - "setup": "npx playwright install && npm ci", + "setup": "npm ci && npx playwright install", "prettier": "npx prettier --check .", "prettier:fix": "npx prettier --write .", "eslint": "npx eslint .", @@ -19,6 +19,7 @@ "test:custom": "ts-node utils/run-custom-tests.ts", "test:report:playwright": "playwright show-report reports/playwright-report", "test:report:custom": "open-cli reports/index.html", + "test:report:detailed": "open-cli reports/detailed-report.html", "prepare": "husky && husky install" }, "lint-staged": { @@ -39,9 +40,9 @@ }, "homepage": "https://github.com/IQGeo/playwright-typescript-template#readme", "devDependencies": { - "@playwright/test": "^1.53.1", - "@types/node": "^24.0.3", "@eslint/js": "^9.24.0", + "@playwright/test": "^1.56.0", + "@types/node": "^24.0.3", "@typescript-eslint/eslint-plugin": "^8.29.1", "@typescript-eslint/parser": "^8.29.1", "eslint": "^9.24.0", diff --git a/src/pages/api/user-page.ts b/src/pages/api/user-page.ts index 8da952c..a54a16e 100644 --- a/src/pages/api/user-page.ts +++ b/src/pages/api/user-page.ts @@ -1,4 +1,5 @@ import { APIRequestContext } from "@playwright/test"; +import { Logger } from "shared/utils"; export class UserApi { private readonly url; @@ -7,12 +8,25 @@ export class UserApi { } async getUser(id: number) { - const response = await this.request.get(`${this.url}/users/${id}`); - return response; + try { + const response = await this.request.get(`${this.url}/users/${id}`); + Logger.info(`Received response for user with ID: ${id}`); + return response; + } catch (error) { + const errorMessage = error instanceof Error ? error : new Error(String(error)); + Logger.error(`Error fetching user with ID: ${id} - ${errorMessage.message}`); + throw errorMessage; + } } - async createUser(data: any) { - const response = await this.request.post(`${this.url}/users`, { data }); - return response; + try { + const response = await this.request.post(`${this.url}/users`, { data }); + Logger.info("Created user successfully"); + return response; + } catch (error) { + const errorMessage = error instanceof Error ? error : new Error(String(error)); + Logger.error(`Error creating user - ${errorMessage.message}`); + throw errorMessage; + } } } diff --git a/src/shared/mock-data/index.ts b/src/shared/mock-data/index.ts index b8282fb..4512ee5 100644 --- a/src/shared/mock-data/index.ts +++ b/src/shared/mock-data/index.ts @@ -1 +1,2 @@ -export * from "./todo-mock-data"; +export * from "./todo-data"; +export * from "./user-data"; diff --git a/src/shared/mock-data/todo-mock-data.ts b/src/shared/mock-data/todo-data.ts similarity index 100% rename from src/shared/mock-data/todo-mock-data.ts rename to src/shared/mock-data/todo-data.ts diff --git a/src/shared/mock-data/user-data.ts b/src/shared/mock-data/user-data.ts new file mode 100644 index 0000000..e0d2b7b --- /dev/null +++ b/src/shared/mock-data/user-data.ts @@ -0,0 +1,6 @@ +import { User } from "shared/types"; + +export const NEW_USER: User = { + name: "John Doe", + email: "john@example.com", +}; diff --git a/src/shared/utils/custom-logger.ts b/src/shared/utils/custom-logger.ts index b783462..d4d7475 100644 --- a/src/shared/utils/custom-logger.ts +++ b/src/shared/utils/custom-logger.ts @@ -1,21 +1,31 @@ -import test from "@playwright/test"; import fs from "fs"; import path from "path"; import { LogLevel } from "shared/types"; export class Logger { - private static logFilePath = path.resolve("logs", "automation.log"); + private static logFilePath = path.join(__dirname, "logs", "automation.log"); - public static write(level: LogLevel, message: string) { + private static log(level: LogLevel, message: string) { const timestamp = new Date().toLocaleString(); const logMessage = `${timestamp} ${level}: ${message}`; console.log(logMessage); + fs.mkdirSync(path.dirname(Logger.logFilePath), { recursive: true }); fs.appendFileSync(Logger.logFilePath, logMessage + "\n"); } -} -// Logger.write("INFO", "Validate ADMS event shows up in the details panel"); + public static info(message: any) { + Logger.log("INFO", message); + } + + public static warn(message: any) { + Logger.log("WARN", message); + } + + public static error(message: any) { + Logger.log("ERROR", message); + } -// await test.step("Validate ADMS event shows up in the details panel", async () => { -// await expect(page.locator("#feature-header").getByText("ADMS Event", { exact: true })).toBeVisible(); -// }); + public static debug(message: any) { + Logger.log("DEBUG", message); + } +} diff --git a/src/tests/api/user.spec.ts b/src/tests/api/user.spec.ts index 271c4c7..8d8a7c4 100644 --- a/src/tests/api/user.spec.ts +++ b/src/tests/api/user.spec.ts @@ -1,7 +1,7 @@ import { test, expect } from "@playwright/test"; -import { TAGS } from "@utils/constants"; +import { TAGS } from "@utils/configuration"; import { UserApi } from "pages/api"; -import { User } from "shared/types"; +import { NEW_USER } from "shared/mock-data/user-data"; test.describe("User API Tests", () => { let userApi: UserApi; @@ -16,10 +16,27 @@ test.describe("User API Tests", () => { tag: TAGS.REGRESSION, }, async () => { - const response = await userApi.getUser(1); - const user = await response.json(); - expect(response.ok()).toBeTruthy(); - expect(user.name).toBeDefined(); + await test.step("Request users api to get user by id and validate results", async () => { + const response = await userApi.getUser(1); + const user = await response.json(); + expect(response.ok()).toBeTruthy(); + expect(user.name).toBeDefined(); + }); + }, + ); + + test( + "should not fetch user by ID - 0 (negative case)", + { + tag: TAGS.REGRESSION, + }, + async () => { + await test.step("Request users api to get user by id and validate results", async () => { + const response = await userApi.getUser(0); + const user = await response.json(); + expect(response.ok()).toBeTruthy(); + expect(user.name).toBeDefined(); + }); }, ); @@ -29,14 +46,12 @@ test.describe("User API Tests", () => { tag: TAGS.INTERNAL, }, async () => { - const newUser: User = { - name: "John Doe", - email: "john@example.com", - }; - const response = await userApi.createUser(newUser); - const user = await response.json(); - expect(response.ok()).toBeTruthy(); - expect(user.name).toBe("John Doe"); + await test.step("Request users api to create a new user and validate results", async () => { + const response = await userApi.createUser(NEW_USER); + const user = await response.json(); + expect(response.ok()).toBeTruthy(); + expect(user.name).toBe("John Doe"); + }); }, ); }); diff --git a/src/tests/ui/fixtures/home-fixture.ts b/src/tests/ui/fixtures/home-fixture.ts index dca48b9..5fd2520 100644 --- a/src/tests/ui/fixtures/home-fixture.ts +++ b/src/tests/ui/fixtures/home-fixture.ts @@ -9,7 +9,9 @@ export const test = base.extend({ homePage: async ({ page }, use) => { const url = process.env.PLAYWRIGHT_DEV_URL; const homePage = new HomePage(page); - await homePage.navigate(url || "/"); + await test.step("Navigate to the home page", async () => { + await homePage.navigate(url || "/"); + }); await use(homePage); }, }); diff --git a/src/tests/ui/fixtures/todo-fixture.ts b/src/tests/ui/fixtures/todo-fixture.ts index be5fb93..baa6a65 100644 --- a/src/tests/ui/fixtures/todo-fixture.ts +++ b/src/tests/ui/fixtures/todo-fixture.ts @@ -9,7 +9,9 @@ export const test = base.extend({ todoPage: async ({ page }, use) => { const url = process.env.DEMO_PLAYWRIGHT_DEV_URL; const todoPage = new TodoPage(page); - await todoPage.navigate(url || "/"); + await test.step("Navigate to the todo page", async () => { + await todoPage.navigate(url || "/"); + }); await use(todoPage); }, }); diff --git a/src/tests/ui/home.spec.ts b/src/tests/ui/home.spec.ts index 3633d78..d6de225 100644 --- a/src/tests/ui/home.spec.ts +++ b/src/tests/ui/home.spec.ts @@ -1,4 +1,4 @@ -import { TAGS } from "@utils/constants"; +import { TAGS } from "@utils/configuration"; import { test } from "./fixtures/home-fixture"; test.describe("Home", () => { @@ -8,7 +8,9 @@ test.describe("Home", () => { tag: TAGS.SMOKE, }, async ({ homePage }) => { - await homePage.expectToHaveTitle(/Playwright/); + await test.step("Verify the home page title", async () => { + await homePage.expectToHaveTitle(/Playwright/); + }); }, ); @@ -18,8 +20,10 @@ test.describe("Home", () => { tag: TAGS.SMOKE, }, async ({ homePage }) => { - await homePage.clickGetStarted(); - await homePage.expectInstallationHeadingVisible(); + await test.step("Click on Get Started link and verify navigation", async () => { + await homePage.clickGetStarted(); + await homePage.expectInstallationHeadingVisible(); + }); }, ); }); diff --git a/src/tests/ui/todo.spec.ts b/src/tests/ui/todo.spec.ts index 4f64800..ea20ed3 100644 --- a/src/tests/ui/todo.spec.ts +++ b/src/tests/ui/todo.spec.ts @@ -1,9 +1,8 @@ -// tests/todo.spec.ts import { LOCAL_STORAGE_ID, TODO_ITEMS } from "shared/mock-data"; import { test } from "./fixtures/todo-fixture"; import { checkNumberOfCompletedTodosInLocalStorage, checkNumberOfItemsInLocalStorage } from "shared/utils"; -import { TAGS } from "@utils/constants"; +import { TAGS } from "@utils/configuration"; test.describe("New Todo", () => { test( @@ -12,12 +11,19 @@ test.describe("New Todo", () => { tag: [TAGS.CUSTOMER, TAGS.REGRESSION], }, async ({ page, todoPage }) => { - await todoPage.addTodo(TODO_ITEMS[0]); - await todoPage.expectTodoTitles([TODO_ITEMS[0]]); - await todoPage.addTodo(TODO_ITEMS[1]); - await todoPage.expectTodoTitles([TODO_ITEMS[0], TODO_ITEMS[1]]); + await test.step("Add first todo item", async () => { + await todoPage.addTodo(TODO_ITEMS[0]); + await todoPage.expectTodoTitles([TODO_ITEMS[0]]); + }); - await checkNumberOfItemsInLocalStorage(page, 2, LOCAL_STORAGE_ID); + await test.step("Add second todo item", async () => { + await todoPage.addTodo(TODO_ITEMS[1]); + await todoPage.expectTodoTitles([TODO_ITEMS[0], TODO_ITEMS[1]]); + }); + + await test.step("Check number of items in local storage should be 2", async () => { + await checkNumberOfItemsInLocalStorage(page, 2, LOCAL_STORAGE_ID); + }); }, ); @@ -27,10 +33,14 @@ test.describe("New Todo", () => { tag: [TAGS.CUSTOMER, TAGS.SMOKE], }, async ({ page, todoPage }) => { - await todoPage.addTodo(TODO_ITEMS[0]); - await todoPage.expectInputCleared(); + await test.step("Add first todo item and validate input field is cleared", async () => { + await todoPage.addTodo(TODO_ITEMS[0]); + await todoPage.expectInputCleared(); + }); - await checkNumberOfItemsInLocalStorage(page, 1, LOCAL_STORAGE_ID); + await test.step("Check number of items in local storage should be 1", async () => { + await checkNumberOfItemsInLocalStorage(page, 1, LOCAL_STORAGE_ID); + }); }, ); @@ -40,25 +50,37 @@ test.describe("New Todo", () => { tag: [TAGS.CUSTOMER, TAGS.REGRESSION], }, async ({ page, todoPage }) => { - await todoPage.createDefaultTodos(TODO_ITEMS); + await test.step("Create default todos", async () => { + await todoPage.createDefaultTodos(TODO_ITEMS); + }); - await todoPage.expectTodoCountText("3 items left"); - await todoPage.expectTodoCount(3); - await todoPage.expectTodoTitles(TODO_ITEMS); + await test.step("Validate the todo count and titles", async () => { + await todoPage.expectTodoCountText("3 items left"); + await todoPage.expectTodoCount(3); + await todoPage.expectTodoTitles(TODO_ITEMS); + }); - await checkNumberOfItemsInLocalStorage(page, 3, LOCAL_STORAGE_ID); + await test.step("Check number of items in local storage should be 3", async () => { + await checkNumberOfItemsInLocalStorage(page, 3, LOCAL_STORAGE_ID); + }); }, ); }); test.describe("Mark all as completed", () => { test.beforeEach(async ({ page, todoPage }) => { - await todoPage.createDefaultTodos(TODO_ITEMS); - await checkNumberOfItemsInLocalStorage(page, 3, LOCAL_STORAGE_ID); + await test.step("Create default todos", async () => { + await todoPage.createDefaultTodos(TODO_ITEMS); + }); + await test.step("Check number of items in local storage should be 3", async () => { + await checkNumberOfItemsInLocalStorage(page, 3, LOCAL_STORAGE_ID); + }); }); test.afterEach(async ({ page }) => { - await checkNumberOfItemsInLocalStorage(page, 3, LOCAL_STORAGE_ID); + await test.step("Check number of items in local storage should be 3", async () => { + await checkNumberOfItemsInLocalStorage(page, 3, LOCAL_STORAGE_ID); + }); }); test( @@ -67,9 +89,14 @@ test.describe("Mark all as completed", () => { tag: [TAGS.INTERNAL, TAGS.SMOKE], }, async ({ page, todoPage }) => { - await todoPage.completeAllTodos(); - await todoPage.expectTodosCompletedState(["completed", "completed", "completed"]); - await checkNumberOfCompletedTodosInLocalStorage(page, 3, LOCAL_STORAGE_ID); + await test.step("Complete all todos", async () => { + await todoPage.completeAllTodos(); + }); + + await test.step("Check all todos are completed", async () => { + await todoPage.expectTodosCompletedState(["completed", "completed", "completed"]); + await checkNumberOfCompletedTodosInLocalStorage(page, 3, LOCAL_STORAGE_ID); + }); }, ); @@ -79,9 +106,11 @@ test.describe("Mark all as completed", () => { tag: [TAGS.INTERNAL, TAGS.REGRESSION], }, async ({ todoPage }) => { - await todoPage.completeAllTodos(); - await todoPage.clearAllCompleted(); - await todoPage.expectTodosCompletedState(["", "", ""]); + await test.step("Complete all todos and then clear all completed", async () => { + await todoPage.completeAllTodos(); + await todoPage.clearAllCompleted(); + await todoPage.expectTodosCompletedState(["", "", ""]); + }); }, ); @@ -91,17 +120,53 @@ test.describe("Mark all as completed", () => { tag: [TAGS.INTERNAL, TAGS.SMOKE], }, async ({ page, todoPage }) => { - await todoPage.completeAllTodos(); - await todoPage.expectToggleAllChecked(true); - await checkNumberOfCompletedTodosInLocalStorage(page, 3, LOCAL_STORAGE_ID); + await test.step("Complete all todos", async () => { + await todoPage.completeAllTodos(); + }); + await test.step("Validate all todos are completed and toggle all is checked", async () => { + await todoPage.expectToggleAllChecked(true); + await checkNumberOfCompletedTodosInLocalStorage(page, 3, LOCAL_STORAGE_ID); + }); - const firstCheckbox = todoPage.getTodoCheckbox(0); - await firstCheckbox.uncheck(); - await todoPage.expectToggleAllChecked(false); + await test.step("Uncheck first todo and validate toggle all is unchecked", async () => { + const firstCheckbox = todoPage.getTodoCheckbox(0); + await firstCheckbox.uncheck(); + await todoPage.expectToggleAllChecked(false); + }); + await test.step("Check first todo and validate toggle all is checked", async () => { + const firstCheckbox = todoPage.getTodoCheckbox(0); + await firstCheckbox.check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3, LOCAL_STORAGE_ID); + await todoPage.expectToggleAllChecked(true); + }); + }, + ); + + test( + "complete all checkbox should update state when items are completed / cleared (fail on purpose)", + { + tag: [TAGS.INTERNAL, TAGS.SMOKE], + }, + async ({ page, todoPage }) => { + await test.step("Complete all todos", async () => { + await todoPage.completeAllTodos(); + }); + await test.step("Validate all todos are completed and toggle all is checked", async () => { + await todoPage.expectToggleAllChecked(true); + await checkNumberOfCompletedTodosInLocalStorage(page, 3, LOCAL_STORAGE_ID); + }); - await firstCheckbox.check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3, LOCAL_STORAGE_ID); - await todoPage.expectToggleAllChecked(true); + await test.step("Uncheck first todo and validate toggle all is unchecked", async () => { + const firstCheckbox = todoPage.getTodoCheckbox(0); + await firstCheckbox.uncheck(); + await todoPage.expectToggleAllChecked(false); + }); + await test.step("Check first todo and validate toggle all is checked", async () => { + const firstCheckbox = todoPage.getTodoCheckbox(0); + await firstCheckbox.check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3, LOCAL_STORAGE_ID); + await todoPage.expectToggleAllChecked(false); // This will fail on purpose + }); }, ); }); diff --git a/utils/constants.ts b/utils/configuration.ts similarity index 75% rename from utils/constants.ts rename to utils/configuration.ts index d026b44..041c3f7 100644 --- a/utils/constants.ts +++ b/utils/configuration.ts @@ -37,5 +37,6 @@ export const MODES = [ { name: "Debug", value: "--debug" }, ]; -export const JIRA_PROJECT_ID = 1000; -export const JIRA_PROJECT_ISSUE_TYPE_ID = 1000; +export const JIRA_PROJECT_ID = 1000; // Replace with your actual JIRA project ID +export const JIRA_PROJECT_ISSUE_TYPE_ID = 1000; // Replace with your actual JIRA issue type ID (e.g., Bug, Task) +export const JIRA_API_BASE_URL = "https://your-domain.atlassian.net"; // Replace with your JIRA instance diff --git a/utils/custom-reporter.ts b/utils/custom-reporter.ts index 31abd1d..028874f 100644 --- a/utils/custom-reporter.ts +++ b/utils/custom-reporter.ts @@ -1,7 +1,7 @@ import { Reporter, TestCase, TestResult } from "@playwright/test/reporter"; import * as fs from "fs"; import * as path from "path"; -import { JIRA_PROJECT_ID, JIRA_PROJECT_ISSUE_TYPE_ID } from "./constants"; +import { JIRA_API_BASE_URL, JIRA_PROJECT_ID, JIRA_PROJECT_ISSUE_TYPE_ID } from "./configuration"; interface TestResultData { suite: string; @@ -103,6 +103,7 @@ export default class CustomReporter implements Reporter { private generateReportRow = (r: TestResultData, idx: number, includeActionButtons: boolean): string => { const projectId = JIRA_PROJECT_ID ?? null; const issueTypeId = JIRA_PROJECT_ISSUE_TYPE_ID ?? null; + const jiraBaseUrl = JIRA_API_BASE_URL ?? null; const encodedData = Buffer.from( JSON.stringify({ @@ -119,7 +120,7 @@ export default class CustomReporter implements Reporter { const bugButtonColumn = includeActionButtons ? r.status === "failed" ? ` - + ` : "" @@ -190,7 +191,7 @@ export default class CustomReporter implements Reporter { - IQGeo Test Report + Test Report