mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 16:22:16 +01:00
Compare commits
4 Commits
v0.12.1
...
feature/e2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
785c8b939f | ||
|
|
b2fa07b38b | ||
|
|
5b053bc2c1 | ||
|
|
b775aaf1df |
@@ -608,7 +608,7 @@ test('test that billable icon shows dollar sign for USD currency on time entry r
|
|||||||
page,
|
page,
|
||||||
ctx,
|
ctx,
|
||||||
}) => {
|
}) => {
|
||||||
await updateOrganizationCurrencyViaWeb(ctx, 'USD');
|
await updateOrganizationCurrencyViaWeb(page, ctx, 'USD');
|
||||||
await goToTimeOverview(page);
|
await goToTimeOverview(page);
|
||||||
await createEmptyTimeEntry(page);
|
await createEmptyTimeEntry(page);
|
||||||
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
||||||
@@ -621,7 +621,7 @@ test('test that billable icon shows euro sign for EUR currency on time entry row
|
|||||||
page,
|
page,
|
||||||
ctx,
|
ctx,
|
||||||
}) => {
|
}) => {
|
||||||
await updateOrganizationCurrencyViaWeb(ctx, 'EUR');
|
await updateOrganizationCurrencyViaWeb(page, ctx, 'EUR');
|
||||||
await goToTimeOverview(page);
|
await goToTimeOverview(page);
|
||||||
await createEmptyTimeEntry(page);
|
await createEmptyTimeEntry(page);
|
||||||
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first();
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ test('test that starting and stopping a timer without description and project wo
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('test that billable icon shows dollar sign for USD currency', async ({ page, ctx }) => {
|
test('test that billable icon shows dollar sign for USD currency', async ({ page, ctx }) => {
|
||||||
await updateOrganizationCurrencyViaWeb(ctx, 'USD');
|
await updateOrganizationCurrencyViaWeb(page, ctx, 'USD');
|
||||||
await goToDashboard(page);
|
await goToDashboard(page);
|
||||||
await page.waitForLoadState('networkidle');
|
await page.waitForLoadState('networkidle');
|
||||||
const billableButton = page.getByRole('button', { name: 'Non Billable' }).first();
|
const billableButton = page.getByRole('button', { name: 'Non Billable' }).first();
|
||||||
@@ -39,7 +39,7 @@ test('test that billable icon shows dollar sign for USD currency', async ({ page
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('test that billable icon shows euro sign for EUR currency', async ({ page, ctx }) => {
|
test('test that billable icon shows euro sign for EUR currency', async ({ page, ctx }) => {
|
||||||
await updateOrganizationCurrencyViaWeb(ctx, 'EUR');
|
await updateOrganizationCurrencyViaWeb(page, ctx, 'EUR');
|
||||||
await goToDashboard(page);
|
await goToDashboard(page);
|
||||||
await page.waitForLoadState('networkidle');
|
await page.waitForLoadState('networkidle');
|
||||||
const billableButton = page.getByRole('button', { name: 'Non Billable' }).first();
|
const billableButton = page.getByRole('button', { name: 'Non Billable' }).first();
|
||||||
|
|||||||
@@ -16,12 +16,59 @@ export interface TestContext {
|
|||||||
// Auth helpers
|
// Auth helpers
|
||||||
// ──────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────
|
||||||
|
|
||||||
async function getApiHeaders(page: Page): Promise<Record<string, string>> {
|
/**
|
||||||
const cookies = await page.context().cookies();
|
* Create a Passport API token by calling the token endpoint from the browser.
|
||||||
const xsrfCookie = cookies.find((c) => c.name === 'XSRF-TOKEN');
|
*
|
||||||
|
* The browser's native fetch includes the laravel_token cookie (set by
|
||||||
|
* CreateFreshApiToken during the dashboard page load), so authentication
|
||||||
|
* is handled by the browser's own cookie jar. The returned Bearer token is
|
||||||
|
* then used for all subsequent API calls, making them independent of cookie state.
|
||||||
|
*
|
||||||
|
* If the first attempt returns 401 (Octane hasn't fully committed the session yet),
|
||||||
|
* we reload the page to trigger a fresh CreateFreshApiToken and retry.
|
||||||
|
*/
|
||||||
|
async function createApiToken(page: Page): Promise<string> {
|
||||||
|
for (let attempt = 0; attempt < 3; attempt++) {
|
||||||
|
const result = await page.evaluate(async (baseUrl) => {
|
||||||
|
const xsrfCookie = document.cookie.split('; ').find((c) => c.startsWith('XSRF-TOKEN='));
|
||||||
|
const xsrfToken = xsrfCookie
|
||||||
|
? decodeURIComponent(xsrfCookie.split('=').slice(1).join('='))
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const res = await fetch(`${baseUrl}/api/v1/users/me/api-tokens`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
'X-XSRF-TOKEN': xsrfToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name: 'playwright-test' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await res.json();
|
||||||
|
return body.data.access_token as string;
|
||||||
|
}, PLAYWRIGHT_BASE_URL);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload to get a fresh laravel_token cookie and retry.
|
||||||
|
// networkidle gives Octane time to fully commit the session.
|
||||||
|
await page.reload({ waitUntil: 'networkidle' });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Failed to create API token after retries');
|
||||||
|
}
|
||||||
|
|
||||||
|
function bearerHeaders(token: string): Record<string, string> {
|
||||||
return {
|
return {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
...(xsrfCookie ? { 'X-XSRF-TOKEN': decodeURIComponent(xsrfCookie.value) } : {}),
|
Authorization: `Bearer ${token}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +77,10 @@ async function getApiHeaders(page: Page): Promise<Record<string, string>> {
|
|||||||
// ──────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function setupTestContext(page: Page): Promise<TestContext> {
|
export async function setupTestContext(page: Page): Promise<TestContext> {
|
||||||
|
const token = await createApiToken(page);
|
||||||
const request = page.request;
|
const request = page.request;
|
||||||
const headers = await getApiHeaders(page);
|
const headers = bearerHeaders(token);
|
||||||
|
|
||||||
const orgId = await getOrganizationId(request, headers);
|
const orgId = await getOrganizationId(request, headers);
|
||||||
const memberId = await getCurrentMemberId(request, orgId, headers);
|
const memberId = await getCurrentMemberId(request, orgId, headers);
|
||||||
return { request: createAuthenticatedRequest(request, headers), orgId, memberId };
|
return { request: createAuthenticatedRequest(request, headers), orgId, memberId };
|
||||||
@@ -491,11 +540,17 @@ export async function updateOrganizationSettingViaApi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateOrganizationCurrencyViaWeb(
|
export async function updateOrganizationCurrencyViaWeb(
|
||||||
|
page: Page,
|
||||||
ctx: TestContext,
|
ctx: TestContext,
|
||||||
currency: string,
|
currency: string,
|
||||||
name: string = 'Test Organization'
|
name: string = 'Test Organization'
|
||||||
) {
|
) {
|
||||||
const response = await ctx.request.put(`${PLAYWRIGHT_BASE_URL}/teams/${ctx.orgId}`, {
|
const cookies = await page.context().cookies();
|
||||||
|
const xsrfCookie = cookies.find((c) => c.name === 'XSRF-TOKEN');
|
||||||
|
const xsrfToken = xsrfCookie ? decodeURIComponent(xsrfCookie.value) : '';
|
||||||
|
|
||||||
|
const response = await page.request.put(`${PLAYWRIGHT_BASE_URL}/teams/${ctx.orgId}`, {
|
||||||
|
headers: { 'X-XSRF-TOKEN': xsrfToken },
|
||||||
data: { name, currency },
|
data: { name, currency },
|
||||||
});
|
});
|
||||||
expect(response.status()).toBe(200);
|
expect(response.status()).toBe(200);
|
||||||
|
|||||||
Reference in New Issue
Block a user