Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion e2e/helpers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ export async function loginAsAdmin(page: Page): Promise<void> {
await loginAs(page, 'admin');
}

/** Logs in as a general (non-admin) user and waits for redirect to home. */
/** Logs in as a general (non-admin) user verified with a fictional AtCoder handle. */
export async function loginAsUser(page: Page): Promise<void> {
await loginAs(page, 'guest');
}

/** Logs in as a general user without AtCoder verification and waits for redirect to home. */
export async function loginAsUnverifiedUser(page: Page): Promise<void> {
await loginAs(page, 'Alice');
}

/**
* Fills and submits the login form without navigating first.
* Use when already on /login?redirectTo=... to preserve the redirect destination.
Expand Down
60 changes: 44 additions & 16 deletions e2e/user_edit.spec.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,73 @@
import { test, expect } from '@playwright/test';
import { loginAsUser } from './helpers/auth';
import { test, expect, type Page } from '@playwright/test';
import { loginAsUnverifiedUser, loginAsUser } from './helpers/auth';

const TIMEOUT = 60 * 1000;
const SHORT_TIMEOUT = 5000;

// Mirrors /users/edit route
const EDIT_PAGE_URL = '/users/edit';

const BASIC_INFO_TAB_NAME = '基本情報';
const ATCODER_TAB_NAME = 'AtCoder IDを設定';

function getTab(page: Page, name: string) {
return page.getByRole('tab', { name });
}

async function openEditPage(page: Page): Promise<void> {
await page.goto(EDIT_PAGE_URL);
// Wait for page to stabilize before running assertions
await expect(getTab(page, BASIC_INFO_TAB_NAME)).toBeVisible({ timeout: TIMEOUT });
}

test.describe('user edit page (/users/edit)', () => {
test('unauthenticated user is redirected to /login', async ({ page }) => {
await page.goto(EDIT_PAGE_URL);
await expect(page).toHaveURL(/\/login/, { timeout: TIMEOUT });
});

test.describe('logged-in user (guest)', () => {
test.describe('logged-in user without AtCoder verification (Alice)', () => {
test.beforeEach(async ({ page }) => {
await loginAsUser(page);
await page.goto(EDIT_PAGE_URL);
// Wait for page to stabilize before running assertions
await expect(page.getByRole('tab', { name: '基本情報' })).toBeVisible({ timeout: TIMEOUT });
await loginAsUnverifiedUser(page);
await openEditPage(page);
});

test('基本情報 tab is active by default', async ({ page }) => {
// Tab visibility already asserted in beforeEach; check active state only
await expect(page.getByRole('tab', { name: '基本情報' })).toHaveAttribute(
'aria-selected',
'true',
{ timeout: SHORT_TIMEOUT },
);
await expect(getTab(page, BASIC_INFO_TAB_NAME)).toHaveAttribute('aria-selected', 'true', {
timeout: SHORT_TIMEOUT,
});
await expect(getTab(page, ATCODER_TAB_NAME)).toHaveAttribute('aria-selected', 'false');
});
});

test.describe('logged-in user with AtCoder verification (guest)', () => {
test.beforeEach(async ({ page }) => {
await loginAsUser(page);
await openEditPage(page);
});

test('username is displayed in the 基本情報 tab', async ({ page }) => {
// Assert the username label input shows the logged-in user's name
test('AtCoder IDを設定 tab is active by default', async ({ page }) => {
await expect(getTab(page, ATCODER_TAB_NAME)).toHaveAttribute('aria-selected', 'true', {
timeout: SHORT_TIMEOUT,
});
await expect(getTab(page, BASIC_INFO_TAB_NAME)).toHaveAttribute('aria-selected', 'false');
});

test('username is displayed after opening the 基本情報 tab', async ({ page }) => {
// The AtCoder tab also renders a ユーザ名 field, so confirm the 基本情報 tab is selected first
await getTab(page, BASIC_INFO_TAB_NAME).click();
await expect(getTab(page, BASIC_INFO_TAB_NAME)).toHaveAttribute('aria-selected', 'true', {
timeout: SHORT_TIMEOUT,
});

await expect(page.getByRole('textbox', { name: 'ユーザ名' })).toHaveValue('guest', {
timeout: SHORT_TIMEOUT,
});
});

test('アカウント削除 tab is not shown for the guest user', async ({ page }) => {
// guest is excluded from account deletion (isGeneralUser returns false for username 'guest')
await expect(page.getByRole('tab', { name: 'アカウント削除' })).not.toBeVisible({
await expect(getTab(page, 'アカウント削除')).not.toBeVisible({
timeout: SHORT_TIMEOUT,
});
});
Expand Down
Loading