diff --git a/pages/LoginPage.ts b/pages/LoginPage.ts new file mode 100644 index 0000000..938cb2f --- /dev/null +++ b/pages/LoginPage.ts @@ -0,0 +1,73 @@ +import { expect, Locator, Page } from '@playwright/test'; + +export class LoginPage { + readonly page: Page; + readonly mainContent: Locator; + readonly usernameInput: Locator; + readonly passwordInput: Locator; + readonly submitButton: Locator; + + constructor(page: Page) { + this.page = page; + this.mainContent = page.locator('main, [role="main"], body').first(); + + this.usernameInput = page + .locator( + 'input[name*="user" i], input[id*="user" i], input[name*="email" i], input[id*="email" i], input[type="text"]' + ) + .first(); + + this.passwordInput = page.locator('input[type="password"]').first(); + + this.submitButton = page + .locator( + 'button[type="submit"], input[type="submit"], button:has-text("Log In"), button:has-text("Login"), button:has-text("Sign In"), input[value*="Log" i], input[value*="Sign" i]' + ) + .first(); + } + + async goto(): Promise { + await this.page.goto('/login', { + waitUntil: 'domcontentloaded', + }); + } + + async expectPageLoaded(): Promise { + await expect(this.page).toHaveURL(/\/login|\/signin|\/users\/login/i); + await expect(this.mainContent).toBeVisible(); + } + + async expectLoginFormVisible(): Promise { + await expect(this.usernameInput).toBeVisible({ timeout: 10000 }); + await expect(this.passwordInput).toBeVisible({ timeout: 10000 }); + await expect(this.submitButton).toBeVisible({ timeout: 10000 }); + } + + async expectAuthenticationRequiredState(): Promise { + const currentUrl = this.page.url(); + + const isLoginUrl = /\/login|\/signin|\/users\/login/i.test(currentUrl); + + const hasLoginText = await this.mainContent + .getByText(/log in|login|sign in|authentication|required/i) + .first() + .isVisible() + .catch(() => false); + + const hasSecurityChallenge = + /__cf_chl_rt_tk|cdn-cgi|challenge/i.test(currentUrl) || + (await this.page + .locator('body') + .getByText( + /checking your browser|verify you are human|security check|cloudflare|just a moment/i + ) + .first() + .isVisible() + .catch(() => false)); + + expect( + isLoginUrl || hasLoginText || hasSecurityChallenge, + `Expected unauthenticated user to be redirected to login, shown an authentication-required message, or blocked by a security challenge. Current URL: ${currentUrl}` + ).toBeTruthy(); +} +} \ No newline at end of file diff --git a/tests/auth/login-page.spec.ts b/tests/auth/login-page.spec.ts new file mode 100644 index 0000000..5628df0 --- /dev/null +++ b/tests/auth/login-page.spec.ts @@ -0,0 +1,19 @@ +import { test } from '@playwright/test'; +import { LoginPage } from '../../pages/LoginPage'; + +test.describe('Discogs login page', () => { + test('@smoke @auth login page loads with expected fields', async ({ + page, + }) => { + const loginPage = new LoginPage(page); + + await test.step('Navigate to login page', async () => { + await loginPage.goto(); + }); + + await test.step('Validate login page and form fields are visible', async () => { + await loginPage.expectPageLoaded(); + await loginPage.expectLoginFormVisible(); + }); + }); +}); \ No newline at end of file diff --git a/tests/auth/unauthenticated-collection.spec.ts b/tests/auth/unauthenticated-collection.spec.ts new file mode 100644 index 0000000..f80d667 --- /dev/null +++ b/tests/auth/unauthenticated-collection.spec.ts @@ -0,0 +1,24 @@ +import { expect, test } from '@playwright/test'; +import { LoginPage } from '../../pages/LoginPage'; + +test.describe('Discogs unauthenticated collection access', () => { + test('@negative @auth unauthenticated user cannot access collection page', async ({ + page, + }) => { + const loginPage = new LoginPage(page); + + await test.step('Navigate directly to account collection area without authentication', async () => { + await page.goto('/my', { + waitUntil: 'domcontentloaded', + }); + }); + + await test.step('Validate user is not allowed into authenticated account area', async () => { + await loginPage.expectAuthenticationRequiredState(); + + await expect(page.locator('body')).not.toContainText( + /estimated collection value|manage folders|export my collection/i + ); + }); + }); +}); \ No newline at end of file