diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 0000000..9c20ddb --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,36 @@ +name: Validation + +on: + push: + branches-ignore: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + validation: + name: Typecheck & Build + runs-on: ubuntu-24.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Typecheck + run: npm run typecheck + + - name: Build + run: npm run build diff --git a/lib/browser.types.ts b/lib/browser.types.ts new file mode 100644 index 0000000..325c857 --- /dev/null +++ b/lib/browser.types.ts @@ -0,0 +1,2 @@ +export type BrowserName = "chrome" | "firefox"; +export type BrowserMode = "headless" | "display"; \ No newline at end of file diff --git a/lib/test.context.ts b/lib/test.context.ts new file mode 100644 index 0000000..ad7e367 --- /dev/null +++ b/lib/test.context.ts @@ -0,0 +1,67 @@ +import { WebDriver } from "selenium-webdriver"; + +import Pages from "@pages/index"; +import { + WebDriverFactory, + type WebDriverFactoryOptions, +} from "@lib/webdriver.factory"; +import { createTestMetadata, type TestMetadata } from "@lib/test.metadata"; +import { recordTestMetadata } from "@lib/test.execution.metadata"; + +export default class TestContext { + private _driver?: WebDriver; + private _pages?: Pages; + private _metadata?: TestMetadata; + + constructor(private readonly options: WebDriverFactoryOptions = {}) {} + + async start(): Promise { + if (this._driver) { + throw new Error("TestContext has already been started."); + } + + const factory = new WebDriverFactory(this.options); + this._driver = await factory.build(); + this._metadata = createTestMetadata(factory.metadata); + this._pages = new Pages(this._driver); + recordTestMetadata(this._metadata); + } + + get driver(): WebDriver { + if (!this._driver) { + throw new Error("TestContext has not been started. Call start() first."); + } + + return this._driver; + } + + get pages(): Pages { + if (!this._pages) { + throw new Error("TestContext has not been started. Call start() first."); + } + + return this._pages; + } + + get metadata(): TestMetadata { + if (!this._metadata) { + throw new Error("TestContext has not been started. Call start() first."); + } + + return this._metadata; + } + + async stop(): Promise { + if (!this._driver) { + return; + } + + try { + await this._driver.quit(); + } finally { + this._driver = undefined; + this._pages = undefined; + this._metadata = undefined; + } + } +} diff --git a/lib/test.execution.metadata.ts b/lib/test.execution.metadata.ts new file mode 100644 index 0000000..fe8c658 --- /dev/null +++ b/lib/test.execution.metadata.ts @@ -0,0 +1,25 @@ +import fs from "node:fs"; +import path from "node:path"; + +import type { TestMetadata } from "@lib/test.metadata"; + +let executionMetadata: TestMetadata | undefined; + +export function recordTestMetadata(metadata: TestMetadata) { + if (executionMetadata) { + return; + } + + executionMetadata = metadata; + const reportsDirectory = path.resolve("reports"); + fs.mkdirSync(reportsDirectory, { recursive: true }); + fs.writeFileSync( + path.join(reportsDirectory, "metadata.json"), + `${JSON.stringify(metadata, null, 2)}\n`, + "utf8", + ); +} + +export function getTestMetadata(): TestMetadata | undefined { + return executionMetadata; +} diff --git a/lib/test.metadata.ts b/lib/test.metadata.ts new file mode 100644 index 0000000..ce69e07 --- /dev/null +++ b/lib/test.metadata.ts @@ -0,0 +1,99 @@ +import os from "node:os"; +import type { BrowserMode, BrowserName } from "@lib/browser.types"; + +export type TestExecutionEnvironment = "local" | "ci"; + +export interface TestMetadataInput { + browser: { + name: BrowserName; + version: string; + mode: BrowserMode; + }; + + platform: { + name: string; + }; +} + +export interface TestMetadata extends TestMetadataInput { + platform: { + name: string; + architecture: string; + }; + + runtime: { + node: string; + selenium: string; + mocha: string; + }; + + execution: { + environment: TestExecutionEnvironment; + startedAt: string; + }; + + sourceControl: { + branch?: string; + commit?: string; + }; +} + +function getExecutionEnvironment(): TestExecutionEnvironment { + return process.env.CI === "true" ? "ci" : "local"; +} + +function getGitBranch(): string | undefined { + return ( + process.env.GITHUB_HEAD_REF || + process.env.GITHUB_REF_NAME || + process.env.GIT_BRANCH || + undefined + ); +} + +function getGitCommit(): string | undefined { + return process.env.GITHUB_SHA || process.env.GIT_COMMIT || undefined; +} + +function getDependencyVersion(packageName: string): string { + try { + const packageJson = require(`${packageName}/package.json`) as { + version: string; + }; + + return packageJson.version; + } catch { + return "unknown"; + } +} + +export function createTestMetadata(input: TestMetadataInput): TestMetadata { + return { + browser: { + name: input.browser.name, + version: input.browser.version, + mode: input.browser.mode, + }, + + platform: { + name: input.platform.name, + architecture: os.arch(), + }, + + runtime: { + node: process.version, + selenium: getDependencyVersion("selenium-webdriver"), + mocha: getDependencyVersion("mocha"), + }, + + execution: { + environment: getExecutionEnvironment(), + startedAt: new Date().toISOString(), + }, + + sourceControl: { + branch: getGitBranch(), + commit: getGitCommit(), + }, + }; +} diff --git a/lib/webdriver.factory.ts b/lib/webdriver.factory.ts index 3e70186..8289f64 100644 --- a/lib/webdriver.factory.ts +++ b/lib/webdriver.factory.ts @@ -1,21 +1,23 @@ import { Builder, WebDriver } from "selenium-webdriver"; import chrome from "selenium-webdriver/chrome"; import firefox from "selenium-webdriver/firefox"; +import type { BrowserMode, BrowserName } from "@lib/browser.types"; +import type { TestMetadataInput } from "@lib/test.metadata"; import env from "@config/env"; -export type BrowserName = "chrome" | "firefox"; -export type BrowserMode = "headless" | "display"; - export interface WebDriverFactoryOptions { name?: BrowserName; mode?: BrowserMode; } export class WebDriverFactory { + private _metadata?: TestMetadataInput; + constructor(private options: WebDriverFactoryOptions = {}) {} async build(): Promise { const { name = env.browser.name, mode = env.browser.mode } = this.options; + const builder = new Builder().forBrowser(name); if (name === "chrome") { @@ -40,6 +42,28 @@ export class WebDriverFactory { builder.setFirefoxOptions(firefoxOptions); } - return await builder.build(); + const driver = await builder.build(); + const capabilities = await driver.getCapabilities(); + + this._metadata = { + browser: { + name: capabilities.get("browserName") as BrowserName, + version: capabilities.get("browserVersion") as string, + mode, + }, + platform: { + name: capabilities.get("platformName") as string, + }, + }; + + return driver; + } + + get metadata(): TestMetadataInput { + if (!this._metadata) { + throw new Error("WebDriverFactory has not built a WebDriver yet."); + } + + return this._metadata; } } diff --git a/pages/admin/auth/index.ts b/pages/admin/auth/index.ts index ecfb186..13a89ce 100644 --- a/pages/admin/auth/index.ts +++ b/pages/admin/auth/index.ts @@ -4,8 +4,8 @@ import LoginPage from "@pages/admin/auth/login.page"; export default class Auth { private readonly loginPage: LoginPage; - constructor(public driver: WebDriver) { - this.loginPage = new LoginPage(driver); + constructor(private driver: WebDriver) { + this.loginPage = new LoginPage(this.driver); } async login() { diff --git a/pages/admin/index.ts b/pages/admin/index.ts index 4c4ca55..b23e3bd 100644 --- a/pages/admin/index.ts +++ b/pages/admin/index.ts @@ -1,6 +1,6 @@ import BasePage from "@pages/base.page"; -export default class Admin extends BasePage { +export default abstract class Admin extends BasePage { get contentHeader() { return this.getDataTest("content-header"); } diff --git a/pages/index.ts b/pages/index.ts index 3f0853e..f1a1e7d 100644 --- a/pages/index.ts +++ b/pages/index.ts @@ -1,6 +1,5 @@ import { WebDriver } from "selenium-webdriver"; -import Admin from "@pages/admin"; import Auth from "@pages/admin/auth"; import CustomerPage from "@pages/admin/customer"; @@ -9,7 +8,6 @@ import LoginPage from "@pages/admin/auth/login.page"; export default class Pages { readonly auth: Auth; - readonly admin: Admin; readonly login: LoginPage; readonly dashboard: DashboardPage; @@ -17,16 +15,9 @@ export default class Pages { constructor(private driver: WebDriver) { this.auth = new Auth(this.driver); - this.admin = new Admin(this.driver); this.login = new LoginPage(this.driver); this.dashboard = new DashboardPage(this.driver); this.customer = new CustomerPage(this.driver); } - - async quit() { - if (this.driver != null) { - await this.driver.quit(); - } - } } diff --git a/tests/admin/customer/create.test.ts b/tests/admin/customer/create.test.ts index cc4fe3f..ca30332 100644 --- a/tests/admin/customer/create.test.ts +++ b/tests/admin/customer/create.test.ts @@ -1,7 +1,6 @@ import { after, before, beforeEach, describe, it } from "mocha"; import { expect } from "chai"; -import { WebDriverFactory } from "@lib/webdriver.factory"; -import Pages from "@pages/index"; +import TestContext from "@lib/test.context"; import customerRepository from "@repositories/customer.repository"; const customerCreatePage = { @@ -44,37 +43,35 @@ const fields = { }; describe("Create Customer", function () { - let page: Pages; + const ctx = new TestContext(); before(async function () { - const driver = await new WebDriverFactory().build(); - page = new Pages(driver); - - await page.auth.login(); + await ctx.start(); + await ctx.pages.auth.login(); }); context("Basic page", function () { before(async function () { - await page.customer.create.open(); + await ctx.pages.customer.create.open(); }); it("shows document title", async function () { - expect(await page.customer.create.title()).to.be.equal( + expect(await ctx.pages.customer.create.title()).to.be.equal( customerCreatePage.title, ); }); it("shows header text", async function () { - expect(await page.customer.create.contentHeader.getText()).to.be.equal( - customerCreatePage.headerText, - ); + expect( + await ctx.pages.customer.create.contentHeader.getText(), + ).to.be.equal(customerCreatePage.headerText); }); customerCreatePage.breadcrumbs.forEach(({ text, link }, index) => { const level = index + 1; it(`shows breadcrumb level ${level}`, async function () { - const breadcrumb = await page.customer.create.breadcrumb(level); + const breadcrumb = await ctx.pages.customer.create.breadcrumb(level); expect(await breadcrumb.getText()).to.be.equal(text); @@ -86,7 +83,7 @@ describe("Create Customer", function () { it("has active class", async function () { expect( - await page.customer.create + await ctx.pages.customer.create .menuActiveClass(customerCreatePage.id) .getAttribute("class"), ).to.contain("active"); @@ -94,14 +91,16 @@ describe("Create Customer", function () { it(`shows menu text '${customerCreatePage.menu}'`, async function () { expect( - await page.customer.create.menuText(customerCreatePage.id).getText(), + await ctx.pages.customer.create + .menuText(customerCreatePage.id) + .getText(), ).to.be.equal(customerCreatePage.menu); }); }); context("Form", function () { beforeEach(async function () { - await page.customer.create.open(); + await ctx.pages.customer.create.open(); }); const mapped = Object.entries(fields).map(([name, field]) => ({ @@ -112,10 +111,10 @@ describe("Create Customer", function () { mapped.forEach((field) => { it(`renders the ${field.label} field`, async function () { expect( - await page.customer.create.label(field.name).getText(), + await ctx.pages.customer.create.label(field.name).getText(), ).to.be.equal(field.label); expect( - await page.customer.create.input(field.name).isDisplayed(), + await ctx.pages.customer.create.input(field.name).isDisplayed(), ).to.be.equal(true); }); }); @@ -123,153 +122,157 @@ describe("Create Customer", function () { context("Validation", function () { beforeEach(async function () { - await page.customer.create.open(); + await ctx.pages.customer.create.open(); }); it("shows required errors when submitted empty", async function () { - await page.customer.create.submitButton.click(); + await ctx.pages.customer.create.submitButton.click(); expect( - await page.customer.create.errorMessage("name").getText(), + await ctx.pages.customer.create.errorMessage("name").getText(), ).to.be.equal(fields.name.required); expect( - await page.customer.create.errorMessage("address").getText(), + await ctx.pages.customer.create.errorMessage("address").getText(), ).to.be.equal(fields.address.required); expect( - await page.customer.create.errorMessage("email").getText(), + await ctx.pages.customer.create.errorMessage("email").getText(), ).to.be.equal(fields.email.required); expect( - await page.customer.create.errorMessage("phone").getText(), + await ctx.pages.customer.create.errorMessage("phone").getText(), ).to.be.equal(fields.phone.required); }); it("shows an error when name is shorter than 3 characters", async function () { - const input = await page.customer.create.input("name"); + const input = await ctx.pages.customer.create.input("name"); await input.sendKeys("aa"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); expect( - await page.customer.create.errorMessage("name").getText(), + await ctx.pages.customer.create.errorMessage("name").getText(), ).to.contain(fields.name.minlength); }); it("accepts a name with exactly 3 characters", async function () { - const input = await page.customer.create.input("name"); + const input = await ctx.pages.customer.create.input("name"); await input.sendKeys("abc"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); - expect(await page.customer.create.hasError("name")).to.not.exist; + expect(await ctx.pages.customer.create.hasError("name")).to.not.exist; }); it("accepts a name with exactly 200 characters", async function () { const name = "a".repeat(200); - const input = await page.customer.create.input("name"); + const input = await ctx.pages.customer.create.input("name"); await input.sendKeys(name); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); expect(await input.getAttribute("value")).to.be.equal(name); - expect(await page.customer.create.hasError("name")).to.not.exist; + expect(await ctx.pages.customer.create.hasError("name")).to.not.exist; }); it("shows an error when name exceeds 200 characters", async function () { const name = "a".repeat(201); - const input = await page.customer.create.input("name"); + const input = await ctx.pages.customer.create.input("name"); await input.sendKeys(name); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); expect( - await page.customer.create.errorMessage("name").getText(), + await ctx.pages.customer.create.errorMessage("name").getText(), ).to.contain(fields.name.maxlength); }); it("shows an error when address is shorter than 3 characters", async function () { - const input = await page.customer.create.input("address"); + const input = await ctx.pages.customer.create.input("address"); await input.sendKeys("aa"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); expect( - await page.customer.create.errorMessage("address").getText(), + await ctx.pages.customer.create.errorMessage("address").getText(), ).to.be.exist.and.contain(fields.address.minlength); }); it("accepts an address with exactly 3 characters", async function () { - const input = await page.customer.create.input("address"); + const input = await ctx.pages.customer.create.input("address"); await input.sendKeys("abc"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); - expect(await page.customer.create.hasError("address")).to.not.exist; + expect(await ctx.pages.customer.create.hasError("address")).to.not.exist; }); it("shows an error for an invalid email", async function () { - const input = await page.customer.create.input("email"); + const input = await ctx.pages.customer.create.input("email"); await input.sendKeys("this.email@"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); expect( - await page.customer.create.errorMessage("email").getText(), + await ctx.pages.customer.create.errorMessage("email").getText(), ).to.contain(fields.email.email); }); it("shows an error when phone is shorter than 9 digits", async function () { - const input = await page.customer.create.input("phone"); + const input = await ctx.pages.customer.create.input("phone"); await input.sendKeys("12345678"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); expect( - await page.customer.create.errorMessage("phone").getText(), + await ctx.pages.customer.create.errorMessage("phone").getText(), ).to.contain(fields.phone.minlength); }); it("accepts a phone number with exactly 9 digits", async function () { - const input = await page.customer.create.input("phone"); + const input = await ctx.pages.customer.create.input("phone"); await input.sendKeys("123456789"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); - expect(await page.customer.create.hasError("phone")).to.not.exist; + expect(await ctx.pages.customer.create.hasError("phone")).to.not.exist; }); it("accepts a phone number with exactly 14 digits", async function () { - const input = await page.customer.create.input("phone"); + const input = await ctx.pages.customer.create.input("phone"); await input.sendKeys("12345678901234"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); - expect(await page.customer.create.hasError("phone")).to.not.exist; + expect(await ctx.pages.customer.create.hasError("phone")).to.not.exist; }); it("shows an error when phone exceeds 14 digits", async function () { - const input = await page.customer.create.input("phone"); + const input = await ctx.pages.customer.create.input("phone"); await input.sendKeys("123456789012345"); - await page.customer.create.blur(input); + await ctx.pages.customer.create.blur(input); expect( - await page.customer.create.errorMessage("phone").getText(), + await ctx.pages.customer.create.errorMessage("phone").getText(), ).to.contain(fields.phone.maxlength); }); }); context("Successful creation", async function () { before(async function () { - await page.customer.create.open(); + await ctx.pages.customer.create.open(); }); it("creates a customer with valid data", async function () { const customer = customerRepository.generateFakeData(); - await page.customer.create.input("name").sendKeys(customer.name); - await page.customer.create.input("address").sendKeys(customer.address); - await page.customer.create.input("email").sendKeys(customer.email); - await page.customer.create.input("phone").sendKeys(customer.phone); - await page.customer.create.submitButton.click(); - - expect(await page.customer.list.url()).to.contain("/admin/customer"); - expect(await page.customer.list.title()).to.equal("Customer"); - expect(await page.customer.list.toast.getText()).to.equal("Data created"); + await ctx.pages.customer.create.input("name").sendKeys(customer.name); + await ctx.pages.customer.create + .input("address") + .sendKeys(customer.address); + await ctx.pages.customer.create.input("email").sendKeys(customer.email); + await ctx.pages.customer.create.input("phone").sendKeys(customer.phone); + await ctx.pages.customer.create.submitButton.click(); + + expect(await ctx.pages.customer.list.url()).to.contain("/admin/customer"); + expect(await ctx.pages.customer.list.title()).to.equal("Customer"); + expect(await ctx.pages.customer.list.toast.getText()).to.equal( + "Data created", + ); - const tableSearch = await page.customer.list.tableSearch; - await page.customer.list.invokeValue(tableSearch, customer.email); - await page.customer.list.triggerKeyUp(tableSearch); + const tableSearch = await ctx.pages.customer.list.tableSearch; + await ctx.pages.customer.list.invokeValue(tableSearch, customer.email); + await ctx.pages.customer.list.triggerKeyUp(tableSearch); const tableData = - await page.customer.list.getManyElement("#table tbody tr"); + await ctx.pages.customer.list.getManyElement("#table tbody tr"); expect(await tableData[0].getText()).to.contain(customer.email); @@ -284,6 +287,6 @@ describe("Create Customer", function () { }); after(async function () { - await page.quit(); + await ctx.stop(); }); }); diff --git a/tests/admin/customer/list.test.ts b/tests/admin/customer/list.test.ts index 5578af8..e92d7b9 100644 --- a/tests/admin/customer/list.test.ts +++ b/tests/admin/customer/list.test.ts @@ -1,23 +1,20 @@ import { after, before, beforeEach, describe, it } from "mocha"; import { expect } from "chai"; import dayjs from "dayjs"; -import Pages from "@pages/index"; -import { WebDriverFactory } from "@lib/webdriver.factory"; +import TestContext from "@lib/test.context"; import customerRepository from "@repositories/customer.repository"; describe("Customer List", function () { - let page: Pages; + const ctx = new TestContext(); before(async function () { - const driver = await new WebDriverFactory().build(); - page = new Pages(driver); - - await page.auth.login(); + await ctx.start(); + await ctx.pages.auth.login(); }); context("Table", function () { beforeEach(async function () { - await page.customer.list.open(); + await ctx.pages.customer.list.open(); }); it("displays the expected table headers", function () { @@ -33,7 +30,7 @@ describe("Customer List", function () { tableHeaders.forEach(async function (value, index) { expect( - await page.customer.list.tableHeaderName(index + 1).getText(), + await ctx.pages.customer.list.tableHeaderName(index + 1).getText(), ).to.equal(value); }); }); @@ -51,7 +48,7 @@ describe("Customer List", function () { expectedData.forEach(async function (value, index) { expect( - await page.customer.list.tableDataName(1, index + 1).getText(), + await ctx.pages.customer.list.tableDataName(1, index + 1).getText(), ).to.be.equal(value); }); }); @@ -59,66 +56,70 @@ describe("Customer List", function () { context("Create Customer", function () { beforeEach(async function () { - await page.customer.list.open(); + await ctx.pages.customer.list.open(); }); it("navigates to the create customer page when Create button is clicked", async function () { - await page.customer.list.addButton.click(); + await ctx.pages.customer.list.addButton.click(); - expect(await page.customer.create.url()).to.contain( + expect(await ctx.pages.customer.create.url()).to.contain( "/admin/customer/create", ); - expect(await page.customer.create.title()).to.be.equal("Create Customer"); + expect(await ctx.pages.customer.create.title()).to.be.equal( + "Create Customer", + ); }); }); context("Update Customer", function () { beforeEach(async function () { - await page.customer.list.open(); + await ctx.pages.customer.list.open(); }); it("navigates to the update customer page when Edit button is clicked", async function () { - await page.customer.list.editButton(1).click(); + await ctx.pages.customer.list.editButton(1).click(); - expect(await page.customer.update.url()).to.include( + expect(await ctx.pages.customer.update.url()).to.include( "/admin/customer/update", ); - expect(await page.customer.update.title()).to.be.equal("Update Customer"); + expect(await ctx.pages.customer.update.title()).to.be.equal( + "Update Customer", + ); }); }); context("Delete Customer", function () { beforeEach(async function () { - await page.customer.list.open(); + await ctx.pages.customer.list.open(); }); it("displays the delete confirmation dialog", async function () { - await page.customer.list.deleteButton(2).click(); + await ctx.pages.customer.list.deleteButton(2).click(); expect( - await page.customer.list.deleteConfirmationModal.isDisplayed(), + await ctx.pages.customer.list.deleteConfirmationModal.isDisplayed(), ).to.be.equal(true); expect( - await page.customer.list.deleteConfirmationConfirmButton.getText(), + await ctx.pages.customer.list.deleteConfirmationConfirmButton.getText(), ).to.be.equal("Yes"); expect( - await page.customer.list.deleteConfirmationCancelButton.getText(), + await ctx.pages.customer.list.deleteConfirmationCancelButton.getText(), ).to.be.equal("No"); }); it("closes the delete confirmation dialog when Cancel is clicked", async function () { - await page.customer.list.deleteButton(2).click(); + await ctx.pages.customer.list.deleteButton(2).click(); expect( - await page.customer.list.deleteConfirmationModal.isDisplayed(), + await ctx.pages.customer.list.deleteConfirmationModal.isDisplayed(), ).to.be.equal(true); - await page.customer.list.deleteConfirmationCancelButton.click(); + await ctx.pages.customer.list.deleteConfirmationCancelButton.click(); const deleteConfirmationModal = - await page.customer.list.deleteConfirmationModal; + await ctx.pages.customer.list.deleteConfirmationModal; expect( - await page.customer.list.waitUntilHidden(deleteConfirmationModal), + await ctx.pages.customer.list.waitUntilHidden(deleteConfirmationModal), ).to.be.equal(true); }); @@ -129,29 +130,29 @@ describe("Customer List", function () { expect(result.affectedRows).to.be.equal(1); expect(result.insertId).to.be.greaterThan(1); - await page.customer.list.reload(); + await ctx.pages.customer.list.reload(); - const tableSearch = await page.customer.list.tableSearch; - await page.customer.list.invokeValue(tableSearch, customer.email); - await page.customer.list.triggerKeyUp(tableSearch); + const tableSearch = await ctx.pages.customer.list.tableSearch; + await ctx.pages.customer.list.invokeValue(tableSearch, customer.email); + await ctx.pages.customer.list.triggerKeyUp(tableSearch); const tableData = - await page.customer.list.getManyElement("#table tbody tr"); + await ctx.pages.customer.list.getManyElement("#table tbody tr"); expect(tableData).to.have.length(1); expect(await tableData[0].getText()).to.contain(customer.email); - await page.customer.list.deleteButtonContains.click(); - const deleteModal = await page.customer.list.deleteConfirmationModal; + await ctx.pages.customer.list.deleteButtonContains.click(); + const deleteModal = await ctx.pages.customer.list.deleteConfirmationModal; expect( - await page.customer.list.waitUntilVisible(deleteModal), + await ctx.pages.customer.list.waitUntilVisible(deleteModal), ).to.be.equal(true); - await page.customer.list.deleteConfirmationConfirmButton.click(); - await page.customer.list.waitUntilStale(deleteModal); + await ctx.pages.customer.list.deleteConfirmationConfirmButton.click(); + await ctx.pages.customer.list.waitUntilStale(deleteModal); - expect(await page.customer.list.url()).to.contain("/admin/customer"); - expect(await page.customer.list.toast.getText()).to.be.equal( + expect(await ctx.pages.customer.list.url()).to.contain("/admin/customer"); + expect(await ctx.pages.customer.list.toast.getText()).to.be.equal( "Data deleted", ); @@ -164,6 +165,6 @@ describe("Customer List", function () { }); after(async function () { - await page.quit(); + await ctx.stop(); }); }); diff --git a/tests/admin/login.test.ts b/tests/admin/login.test.ts index d2c5627..7d16fc8 100644 --- a/tests/admin/login.test.ts +++ b/tests/admin/login.test.ts @@ -1,62 +1,59 @@ import { afterEach, beforeEach, describe, it } from "mocha"; import { expect } from "chai"; -import { WebDriverFactory } from "@lib/webdriver.factory"; -import Pages from "@pages/index"; +import TestContext from "@lib/test.context"; import env from "@config/env"; describe("Login", function () { - let page: Pages; + const ctx = new TestContext(); beforeEach(async function () { - const driver = await new WebDriverFactory().build(); - page = new Pages(driver); - - await page.login.open(); + await ctx.start(); + await ctx.pages.login.open(); }); it("displays the login page", async function () { - expect(await page.login.title()).to.be.equal("Login Admin"); - expect(await page.login.greetings.getText()).to.be.equal( + expect(await ctx.pages.login.title()).to.be.equal("Login Admin"); + expect(await ctx.pages.login.greetings.getText()).to.be.equal( "Login to start your session", ); }); it("shows required validation errors when submitting an empty form", async function () { - await page.login.loginButton.click(); + await ctx.pages.login.loginButton.click(); - expect(await page.login.errorMessage("email").getText()).to.be.equal( + expect(await ctx.pages.login.errorMessage("email").getText()).to.be.equal( "Email required", ); - expect(await page.login.errorMessage("password").getText()).to.be.equal( - "Password required", - ); + expect( + await ctx.pages.login.errorMessage("password").getText(), + ).to.be.equal("Password required"); }); it("shows an error when email format is invalid", async function () { - await page.login.input("email").sendKeys("invalid-email"); - await page.login.input("password").sendKeys("some-password"); - await page.login.loginButton.click(); + await ctx.pages.login.input("email").sendKeys("invalid-email"); + await ctx.pages.login.input("password").sendKeys("some-password"); + await ctx.pages.login.loginButton.click(); - expect(await page.login.errorMessage("email").getText()).to.be.equal( + expect(await ctx.pages.login.errorMessage("email").getText()).to.be.equal( "Email must be a valid email address", ); }); it("logs in successfully with valid credentials", async function () { - const sessionBeforeLogin = await page.login.getCookie( + const sessionBeforeLogin = await ctx.pages.login.getCookie( "BACKENDSESSIONID_PFT", ); - await page.login.input("email").sendKeys(env.login.email); - await page.login.input("password").sendKeys(env.login.password); - await page.login.loginButton.click(); + await ctx.pages.login.input("email").sendKeys(env.login.email); + await ctx.pages.login.input("password").sendKeys(env.login.password); + await ctx.pages.login.loginButton.click(); - expect(await page.dashboard.url()).to.contain("/admin"); - expect(await page.dashboard.title()).to.be.equal("Dashboard"); - expect(await page.dashboard.toast.getText()).to.be.equal( + expect(await ctx.pages.dashboard.url()).to.contain("/admin"); + expect(await ctx.pages.dashboard.title()).to.be.equal("Dashboard"); + expect(await ctx.pages.dashboard.toast.getText()).to.be.equal( `Welcome ${env.login.name}`, ); - const sessionAfterLogin = await page.dashboard.getCookie( + const sessionAfterLogin = await ctx.pages.dashboard.getCookie( "BACKENDSESSIONID_PFT", ); @@ -66,6 +63,6 @@ describe("Login", function () { }); afterEach(async function () { - await page.quit(); + await ctx.stop(); }); }); diff --git a/tests/admin/logout.test.ts b/tests/admin/logout.test.ts index 6445083..0955ca3 100644 --- a/tests/admin/logout.test.ts +++ b/tests/admin/logout.test.ts @@ -1,55 +1,54 @@ import { afterEach, beforeEach, describe, it } from "mocha"; import { expect } from "chai"; -import { WebDriverFactory } from "@lib/webdriver.factory"; -import Pages from "@pages/index"; +import TestContext from "@lib/test.context"; describe("Logout", function () { - let page: Pages; + const ctx = new TestContext(); beforeEach(async function () { - const driver = await new WebDriverFactory().build(); - page = new Pages(driver); - - await page.auth.login(); - await page.dashboard.open(); + await ctx.start(); + await ctx.pages.auth.login(); + await ctx.pages.dashboard.open(); }); it("redirects to the login page after logout", async function () { - await page.dashboard.controlSidebarButton.click(); + await ctx.pages.dashboard.controlSidebarButton.click(); - const controlSidebar = await page.dashboard.controlSidebar; - expect(await page.dashboard.waitUntilVisible(controlSidebar)).to.be.equal( - true, - ); + const controlSidebar = await ctx.pages.dashboard.controlSidebar; + expect( + await ctx.pages.dashboard.waitUntilVisible(controlSidebar), + ).to.be.equal(true); - const logoutButton = page.dashboard.logoutButton; - await page.dashboard.waitUntilInteractable(logoutButton); + const logoutButton = ctx.pages.dashboard.logoutButton; + await ctx.pages.dashboard.waitUntilInteractable(logoutButton); await logoutButton.click(); - await page.dashboard.waitUntilHidden(logoutButton); + await ctx.pages.dashboard.waitUntilHidden(logoutButton); - expect(await page.login.url()).to.include("/admin/auth/login"); + expect(await ctx.pages.login.url()).to.include("/admin/auth/login"); }); it("prevents access to protected pages after logout", async function () { - await page.dashboard.controlSidebarButton.click(); + await ctx.pages.dashboard.controlSidebarButton.click(); - const controlSidebar = await page.dashboard.controlSidebar; - await page.dashboard.waitUntilVisible(controlSidebar); + const controlSidebar = await ctx.pages.dashboard.controlSidebar; + await ctx.pages.dashboard.waitUntilVisible(controlSidebar); - const logoutButton = page.dashboard.logoutButton; - await page.dashboard.waitUntilInteractable(logoutButton); + const logoutButton = ctx.pages.dashboard.logoutButton; + await ctx.pages.dashboard.waitUntilInteractable(logoutButton); await logoutButton.click(); - await page.dashboard.waitUntilHidden(logoutButton); + await ctx.pages.dashboard.waitUntilHidden(logoutButton); - expect(await page.login.url()).to.include("/admin/auth/login"); + expect(await ctx.pages.login.url()).to.include("/admin/auth/login"); - await page.dashboard.open(); + await ctx.pages.dashboard.open(); - expect(await page.login.url()).to.include("/admin/auth/login"); - expect(await page.login.toast.getText()).to.be.equal("You need to login"); + expect(await ctx.pages.login.url()).to.include("/admin/auth/login"); + expect(await ctx.pages.login.toast.getText()).to.be.equal( + "You need to login", + ); }); afterEach(async function () { - await page.quit(); + await ctx.stop(); }); });