-
Notifications
You must be signed in to change notification settings - Fork 44.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tests): add baseline utility for integration testing from fronte…
…nd ui (#8765)
- Loading branch information
Showing
8 changed files
with
279 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,45 @@ | ||
import { test, expect } from "./fixtures"; | ||
// auth.spec.ts | ||
import { test } from "./fixtures"; | ||
|
||
test.describe("Authentication", () => { | ||
test("user can login successfully", async ({ page, loginPage, testUser }) => { | ||
await page.goto("/login"); // Make sure we're on the login page | ||
await page.goto("/login"); | ||
await loginPage.login(testUser.email, testUser.password); | ||
// expect to be redirected to the home page | ||
await expect(page).toHaveURL("/"); | ||
// expect to see the Monitor text | ||
await expect(page.getByText("Monitor")).toBeVisible(); | ||
await test.expect(page).toHaveURL("/"); | ||
await test.expect(page.getByText("Monitor")).toBeVisible(); | ||
}); | ||
|
||
test("user can logout successfully", async ({ | ||
page, | ||
loginPage, | ||
testUser, | ||
}) => { | ||
await page.goto("/login"); // Make sure we're on the login page | ||
await page.goto("/login"); | ||
await loginPage.login(testUser.email, testUser.password); | ||
|
||
// Expect to be on the home page | ||
await expect(page).toHaveURL("/"); | ||
await test.expect(page).toHaveURL("/"); | ||
|
||
// Click on the user menu | ||
await page.getByRole("button", { name: "CN" }).click(); | ||
// Click on the logout menu item | ||
await page.getByRole("menuitem", { name: "Log out" }).click(); | ||
// Expect to be redirected to the login page | ||
await expect(page).toHaveURL("/login"); | ||
|
||
await test.expect(page).toHaveURL("/login"); | ||
}); | ||
|
||
test("login in, then out, then in again", async ({ | ||
page, | ||
loginPage, | ||
testUser, | ||
}) => { | ||
await page.goto("/login"); // Make sure we're on the login page | ||
await page.goto("/login"); | ||
await loginPage.login(testUser.email, testUser.password); | ||
await page.goto("/"); | ||
await page.getByRole("button", { name: "CN" }).click(); | ||
await page.getByRole("menuitem", { name: "Log out" }).click(); | ||
await expect(page).toHaveURL("/login"); | ||
await test.expect(page).toHaveURL("/login"); | ||
await loginPage.login(testUser.email, testUser.password); | ||
await expect(page).toHaveURL("/"); | ||
await expect(page.getByText("Monitor")).toBeVisible(); | ||
await test.expect(page).toHaveURL("/"); | ||
await test.expect(page.getByText("Monitor")).toBeVisible(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,109 @@ | ||
/* eslint-disable react-hooks/rules-of-hooks */ | ||
import { test as base } from "@playwright/test"; | ||
import { createTestUserFixture } from "./test-user.fixture"; | ||
import { createLoginPageFixture } from "./login-page.fixture"; | ||
import type { TestUser } from "./test-user.fixture"; | ||
import { createClient, SupabaseClient } from "@supabase/supabase-js"; | ||
import { faker } from "@faker-js/faker"; | ||
import fs from "fs"; | ||
import path from "path"; | ||
import { TestUser } from "./test-user.fixture"; | ||
import { LoginPage } from "../pages/login.page"; | ||
|
||
type Fixtures = { | ||
// Extend both worker state and test-specific fixtures | ||
type WorkerFixtures = { | ||
workerAuth: TestUser; | ||
}; | ||
|
||
type TestFixtures = { | ||
testUser: TestUser; | ||
loginPage: LoginPage; | ||
}; | ||
|
||
// Combine fixtures | ||
export const test = base.extend<Fixtures>({ | ||
testUser: createTestUserFixture, | ||
loginPage: createLoginPageFixture, | ||
let supabase: SupabaseClient; | ||
|
||
function getSupabaseAdmin() { | ||
if (!supabase) { | ||
supabase = createClient( | ||
process.env.SUPABASE_URL!, | ||
process.env.SUPABASE_SERVICE_ROLE_KEY!, | ||
{ | ||
auth: { | ||
autoRefreshToken: false, | ||
persistSession: false, | ||
}, | ||
}, | ||
); | ||
} | ||
return supabase; | ||
} | ||
|
||
export const test = base.extend<TestFixtures, WorkerFixtures>({ | ||
// Define the worker-level fixture that creates and manages worker-specific auth | ||
workerAuth: [ | ||
async ({}, use, workerInfo) => { | ||
const workerId = workerInfo.workerIndex; | ||
const fileName = path.resolve( | ||
process.cwd(), | ||
`.auth/worker-${workerId}.json`, | ||
); | ||
|
||
// Create directory if it doesn't exist | ||
const dirPath = path.dirname(fileName); | ||
if (!fs.existsSync(dirPath)) { | ||
fs.mkdirSync(dirPath, { recursive: true }); | ||
} | ||
|
||
let auth: TestUser; | ||
if (fs.existsSync(fileName)) { | ||
auth = JSON.parse(fs.readFileSync(fileName, "utf-8")); | ||
} else { | ||
// Generate new worker-specific test user | ||
auth = { | ||
email: `test.worker.${workerId}.${Date.now()}@example.com`, | ||
password: faker.internet.password({ length: 12 }), | ||
}; | ||
|
||
const supabase = getSupabaseAdmin(); | ||
const { | ||
data: { user }, | ||
error: signUpError, | ||
} = await supabase.auth.signUp({ | ||
email: auth.email, | ||
password: auth.password, | ||
}); | ||
|
||
if (signUpError) { | ||
throw signUpError; | ||
} | ||
|
||
auth.id = user?.id; | ||
fs.writeFileSync(fileName, JSON.stringify(auth)); | ||
} | ||
|
||
await use(auth); | ||
|
||
// Cleanup code is commented out to preserve test users during development | ||
/* | ||
if (workerInfo.project.metadata.teardown) { | ||
if (auth.id) { | ||
await deleteTestUser(auth.id); | ||
} | ||
if (fs.existsSync(fileName)) { | ||
fs.unlinkSync(fileName); | ||
} | ||
} | ||
*/ | ||
}, | ||
{ scope: "worker" }, | ||
], | ||
|
||
// Define the test-level fixture that provides access to the worker auth | ||
testUser: async ({ workerAuth }, use) => { | ||
await use(workerAuth); | ||
}, | ||
|
||
// Update login page fixture to use worker auth by default | ||
loginPage: async ({ page }, use) => { | ||
await use(new LoginPage(page)); | ||
}, | ||
}); | ||
|
||
export { expect } from "@playwright/test"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Page } from "@playwright/test"; | ||
import { NavBar } from "./navbar.page"; | ||
|
||
export class BasePage { | ||
readonly navbar: NavBar; | ||
|
||
constructor(protected page: Page) { | ||
this.navbar = new NavBar(page); | ||
} | ||
|
||
async waitForPageLoad() { | ||
// Common page load waiting logic | ||
await this.page.waitForLoadState("networkidle", { timeout: 10000 }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { Page } from "@playwright/test"; | ||
|
||
export class NavBar { | ||
constructor(private page: Page) {} | ||
|
||
async clickProfileLink() { | ||
// await this.page.getByTestId("profile-link").click(); | ||
|
||
await this.page.getByRole("button", { name: "CN" }).click(); | ||
await this.page.getByRole("menuitem", { name: "Profile" }).click(); | ||
} | ||
|
||
async clickMonitorLink() { | ||
await this.page.getByTestId("monitor-link").click(); | ||
} | ||
|
||
async clickBuildLink() { | ||
await this.page.getByTestId("build-link").click(); | ||
} | ||
|
||
async clickMarketplaceLink() { | ||
await this.page.getByTestId("marketplace-link").click(); | ||
} | ||
|
||
async getUserMenuButton() { | ||
return this.page.getByRole("button", { name: "CN" }); | ||
} | ||
|
||
async clickUserMenu() { | ||
await (await this.getUserMenuButton()).click(); | ||
} | ||
|
||
async logout() { | ||
await this.clickUserMenu(); | ||
await this.page.getByRole("menuitem", { name: "Log out" }).click(); | ||
} | ||
|
||
async isLoggedIn(): Promise<boolean> { | ||
try { | ||
await ( | ||
await this.getUserMenuButton() | ||
).waitFor({ | ||
state: "visible", | ||
timeout: 5000, | ||
}); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Page } from "@playwright/test"; | ||
import { BasePage } from "./base.page"; | ||
|
||
export class ProfilePage extends BasePage { | ||
constructor(page: Page) { | ||
super(page); | ||
} | ||
|
||
async getDisplayedEmail(): Promise<string> { | ||
await this.waitForPageToLoad(); | ||
const email = await this.page.getByTestId("profile-email").textContent(); | ||
if (!email) { | ||
throw new Error("Email not found"); | ||
} | ||
return email; | ||
} | ||
|
||
async isLoaded(): Promise<boolean> { | ||
try { | ||
await this.waitForPageToLoad(); | ||
return true; | ||
} catch (error) { | ||
console.error("Error loading profile page", error); | ||
return false; | ||
} | ||
} | ||
|
||
private async waitForPageToLoad(): Promise<void> { | ||
await this.page.waitForLoadState("networkidle", { timeout: 60_000 }); | ||
|
||
await this.page.getByTestId("profile-email").waitFor({ | ||
state: "visible", | ||
timeout: 60_000, | ||
}); | ||
|
||
await this.page.waitForLoadState("networkidle", { timeout: 60_000 }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// profile.spec.ts | ||
import { test } from "./fixtures"; | ||
import { ProfilePage } from "./pages/profile.page"; | ||
|
||
test.describe("Profile", () => { | ||
let profilePage: ProfilePage; | ||
|
||
test.beforeEach(async ({ page, loginPage, testUser }) => { | ||
profilePage = new ProfilePage(page); | ||
|
||
// Start each test with login using worker auth | ||
await page.goto("/login"); | ||
await loginPage.login(testUser.email, testUser.password); | ||
await test.expect(page).toHaveURL("/"); | ||
}); | ||
|
||
test("user can view their profile information", async ({ | ||
page, | ||
testUser, | ||
}) => { | ||
await profilePage.navbar.clickProfileLink(); | ||
// workaround for #8788 | ||
// sleep for 10 seconds to allow page to load due to bug in our system | ||
await page.waitForTimeout(10000); | ||
await page.reload(); | ||
await page.reload(); | ||
await test.expect(profilePage.isLoaded()).resolves.toBeTruthy(); | ||
await test.expect(page).toHaveURL(new RegExp("/profile")); | ||
|
||
// Verify email matches test worker's email | ||
const displayedEmail = await profilePage.getDisplayedEmail(); | ||
test.expect(displayedEmail).toBe(testUser.email); | ||
}); | ||
|
||
test("profile navigation is accessible from navbar", async ({ page }) => { | ||
await profilePage.navbar.clickProfileLink(); | ||
await test.expect(page).toHaveURL(new RegExp("/profile")); | ||
// workaround for #8788 | ||
await page.reload(); | ||
await page.reload(); | ||
await test.expect(profilePage.isLoaded()).resolves.toBeTruthy(); | ||
}); | ||
|
||
test("profile displays user Credential providers", async ({ page }) => { | ||
await profilePage.navbar.clickProfileLink(); | ||
|
||
// await test | ||
// .expect(page.getByTestId("profile-section-personal")) | ||
// .toBeVisible(); | ||
// await test | ||
// .expect(page.getByTestId("profile-section-settings")) | ||
// .toBeVisible(); | ||
// await test | ||
// .expect(page.getByTestId("profile-section-security")) | ||
// .toBeVisible(); | ||
}); | ||
}); |