mirror of https://github.com/buster-so/buster.git
playwright config are update to date
This commit is contained in:
parent
0675ec2fa7
commit
9e29ffbb59
|
@ -15,7 +15,9 @@
|
|||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:debug": "playwright test --debug",
|
||||
"test:e2e:codegen": "playwright codegen"
|
||||
"test:e2e:codegen": "playwright codegen",
|
||||
"test:e2e:clear-auth": "node -e \"require('fs').existsSync('./playwright-tests/auth.json') && require('fs').unlinkSync('./playwright-tests/auth.json') && console.log('Auth state cleared') || console.log('No auth state to clear')\"",
|
||||
"test:e2e:setup-auth": "npx ts-node playwright-tests/auth-setup.ts"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.9.0"
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
import { Page } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
|
||||
// Path to the authentication state file
|
||||
export const authFile = path.join(__dirname, 'auth.json');
|
||||
|
||||
/**
|
||||
* Checks if valid authentication data exists
|
||||
*/
|
||||
export function hasValidAuth(): boolean {
|
||||
try {
|
||||
if (!fs.existsSync(authFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const authData = JSON.parse(fs.readFileSync(authFile, 'utf-8'));
|
||||
|
||||
if (
|
||||
isEmpty(authData) ||
|
||||
isEmpty(authData.cookies) ||
|
||||
isEmpty(authData.localStorage) ||
|
||||
isEmpty(authData.sessionStorage)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if JWT is valid
|
||||
if (authData.localStorage) {
|
||||
const storage = JSON.parse(authData.localStorage);
|
||||
const token = storage.buster_token || storage.token;
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const decoded = jwtDecode(token);
|
||||
const expTime = decoded.exp ? decoded.exp * 1000 : 0; // Convert to milliseconds
|
||||
|
||||
if (expTime && expTime < Date.now()) {
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs login and saves authentication state
|
||||
*/
|
||||
export async function login(page: Page) {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
|
||||
// Add your login logic here, for example:
|
||||
// await page.fill('input[name="email"]', process.env.TEST_USER_EMAIL || 'test@example.com');
|
||||
// await page.fill('input[name="password"]', process.env.TEST_USER_PASSWORD || 'password123');
|
||||
// await page.click('button[type="submit"]');
|
||||
|
||||
await page.getByText('Sign in').click();
|
||||
await page.getByRole('textbox', { name: 'What is your email address?' }).fill('chad@buster.so');
|
||||
await page.getByRole('textbox', { name: 'What is your email address?' }).press('Tab');
|
||||
await page.getByRole('textbox', { name: 'Password' }).fill('password');
|
||||
await page.getByRole('button', { name: 'Sign in' }).click();
|
||||
await page.waitForURL('http://localhost:3000/app/home');
|
||||
|
||||
// Save authentication data
|
||||
const authData = {
|
||||
cookies: await page.context().cookies(),
|
||||
localStorage: await page.evaluate(() => JSON.stringify(localStorage)),
|
||||
sessionStorage: await page.evaluate(() => JSON.stringify(sessionStorage))
|
||||
};
|
||||
|
||||
try {
|
||||
fs.writeFileSync(authFile, JSON.stringify(authData));
|
||||
return authData;
|
||||
} catch (error) {
|
||||
console.error('Failed to save authentication data:', error);
|
||||
return authData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies saved authentication state to a page
|
||||
*/
|
||||
export async function applyAuth(page: Page): Promise<boolean> {
|
||||
if (!hasValidAuth()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const authData = JSON.parse(fs.readFileSync(authFile, 'utf-8'));
|
||||
|
||||
// Add cookies
|
||||
await page.context().addCookies(authData.cookies || []);
|
||||
|
||||
// Set localStorage and sessionStorage
|
||||
if (!isEmpty(authData.localStorage) || !isEmpty(authData.sessionStorage)) {
|
||||
await page.goto('http://localhost:3000');
|
||||
|
||||
if (authData.localStorage) {
|
||||
await page.evaluate((storageData) => {
|
||||
const storage = JSON.parse(storageData);
|
||||
for (const [key, value] of Object.entries(storage)) {
|
||||
localStorage.setItem(key, value as string);
|
||||
}
|
||||
}, authData.localStorage);
|
||||
}
|
||||
|
||||
if (authData.sessionStorage) {
|
||||
await page.evaluate((storageData) => {
|
||||
const storage = JSON.parse(storageData);
|
||||
for (const [key, value] of Object.entries(storage)) {
|
||||
sessionStorage.setItem(key, value as string);
|
||||
}
|
||||
}, authData.sessionStorage);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to apply authentication:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears saved authentication data
|
||||
*/
|
||||
export function clearAuth() {
|
||||
try {
|
||||
if (fs.existsSync(authFile)) {
|
||||
fs.unlinkSync(authFile);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Failed to clear authentication data:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"cookies":[{"name":"sb-127-auth-token","value":"base64-eyJhY2Nlc3NfdG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKcGMzTWlPaUpvZEhSd09pOHZNVEkzTGpBdU1DNHhPalUwTXpJeEwyRjFkR2d2ZGpFaUxDSnpkV0lpT2lKak1tUmtOalJqWkMxbU4yWXpMVFE0T0RRdFltTTVNUzFrTkRaaFpUUXpNVGt3TVdVaUxDSmhkV1FpT2lKaGRYUm9aVzUwYVdOaGRHVmtJaXdpWlhod0lqb3hOelEyTVRNM09UWTFMQ0pwWVhRaU9qRTNORFl4TXpRek5qVXNJbVZ0WVdsc0lqb2lZMmhoWkVCaWRYTjBaWEl1YzI4aUxDSndhRzl1WlNJNklpSXNJbUZ3Y0Y5dFpYUmhaR0YwWVNJNmV5SndjbTkyYVdSbGNpSTZJbVZ0WVdsc0lpd2ljSEp2ZG1sa1pYSnpJanBiSW1WdFlXbHNJbDE5TENKMWMyVnlYMjFsZEdGa1lYUmhJanA3ZlN3aWNtOXNaU0k2SW1GMWRHaGxiblJwWTJGMFpXUWlMQ0poWVd3aU9pSmhZV3d4SWl3aVlXMXlJanBiZXlKdFpYUm9iMlFpT2lKd1lYTnpkMjl5WkNJc0luUnBiV1Z6ZEdGdGNDSTZNVGMwTmpFek5ETTJOWDFkTENKelpYTnphVzl1WDJsa0lqb2lORGhoTURnMlpHSXROVGswT0MwME5tSmpMV0V6TlRFdE5qbGtaakkxT0RVd1l6bGpJaXdpYVhOZllXNXZibmx0YjNWeklqcG1ZV3h6WlgwLjUwdW1fUWdxSlA2bXRJSjlRdHJDUzFYcUhDa09uUGp4OXdrOGdzbFVCR0kiLCJ0b2tlbl90eXBlIjoiYmVhcmVyIiwiZXhwaXJlc19pbiI6MzYwMCwiZXhwaXJlc19hdCI6MTc0NjEzNzk2NSwicmVmcmVzaF90b2tlbiI6ImlqNzZ6ZGtpdGtlciIsInVzZXIiOnsiaWQiOiJjMmRkNjRjZC1mN2YzLTQ4ODQtYmM5MS1kNDZhZTQzMTkwMWUiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJlbWFpbCI6ImNoYWRAYnVzdGVyLnNvIiwiZW1haWxfY29uZmlybWVkX2F0IjoiMjAyNS0wMy0wNFQxODo0MjowNS44MDE2OTdaIiwicGhvbmUiOiIiLCJjb25maXJtZWRfYXQiOiIyMDI1LTAzLTA0VDE4OjQyOjA1LjgwMTY5N1oiLCJsYXN0X3NpZ25faW5fYXQiOiIyMDI1LTA1LTAxVDIxOjE5OjI1LjMzNDgyMDI2WiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7fSwiaWRlbnRpdGllcyI6W3siaWRlbnRpdHlfaWQiOiJjMmRkNjRjZC1mN2YzLTQ4ODQtYmM5MS1kNDZhZTQzMTkwMWUiLCJpZCI6ImMyZGQ2NGNkLWY3ZjMtNDg4NC1iYzkxLWQ0NmFlNDMxOTAxZSIsInVzZXJfaWQiOiJjMmRkNjRjZC1mN2YzLTQ4ODQtYmM5MS1kNDZhZTQzMTkwMWUiLCJpZGVudGl0eV9kYXRhIjp7InN1YiI6ImMyZGQ2NGNkLWY3ZjMtNDg4NC1iYzkxLWQ0NmFlNDMxOTAxZSJ9LCJwcm92aWRlciI6ImVtYWlsIiwibGFzdF9zaWduX2luX2F0IjoiMjAyNS0wMy0wNFQxODo0MjowNS44MTQyNVoiLCJjcmVhdGVkX2F0IjoiMjAyNS0wMy0wNFQxODo0MjowNS44MTQyNVoiLCJ1cGRhdGVkX2F0IjoiMjAyNS0wMy0wNFQxODo0MjowNS44MTQyNVoifV0sImNyZWF0ZWRfYXQiOiIyMDI1LTAzLTA0VDE4OjQyOjA1LjgwMTY5N1oiLCJ1cGRhdGVkX2F0IjoiMjAyNS0wNS0wMVQyMToxOToyNS4zMzc5OTVaIiwiaXNfYW5vbnltb3VzIjpmYWxzZX19","domain":"localhost","path":"/","expires":1746739172.173069,"httpOnly":true,"secure":false,"sameSite":"Lax"}],"localStorage":"{}","sessionStorage":"{}"}
|
|
@ -0,0 +1,37 @@
|
|||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const homePage = createBusterRoute({
|
||||
route: BusterRoutes.APP_HOME
|
||||
});
|
||||
const loginPage = createBusterRoute({
|
||||
route: BusterRoutes.AUTH_LOGIN
|
||||
});
|
||||
|
||||
test.describe('Authentication Flow', () => {
|
||||
test('should redirect when cookies are cleared', async ({ page, context }) => {
|
||||
// First visit home page
|
||||
await page.goto(homePage);
|
||||
await expect(page).toHaveURL(homePage);
|
||||
|
||||
// Clear cookies to remove authentication
|
||||
await context.clearCookies();
|
||||
|
||||
// Try to access the protected home page again
|
||||
await page.goto(homePage);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Should be redirected away from the protected route
|
||||
await expect(page).not.toHaveURL(homePage);
|
||||
await expect(page).toHaveURL(loginPage);
|
||||
});
|
||||
|
||||
test('go to home page', async ({ page }) => {
|
||||
await page.goto(homePage);
|
||||
|
||||
//for 100 milliseconds
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
await expect(page).toHaveURL(homePage);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
import { chromium, FullConfig } from '@playwright/test';
|
||||
import { applyAuth, login, authFile, hasValidAuth } from './auth-utils';
|
||||
import * as fs from 'fs';
|
||||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
// Make sure auth file exists with at least empty valid JSON to prevent errors
|
||||
if (!fs.existsSync(authFile)) {
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
authFile,
|
||||
JSON.stringify({
|
||||
cookies: [],
|
||||
localStorage: '{}',
|
||||
sessionStorage: '{}'
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to create initial auth file:', error);
|
||||
// Continue - we'll handle login below
|
||||
}
|
||||
}
|
||||
|
||||
// Use chromium browser for the setup
|
||||
const browser = await chromium.launch({ headless: false });
|
||||
const page = await browser.newPage();
|
||||
console.log('global setup page');
|
||||
|
||||
// Check if we have valid stored credentials
|
||||
if (hasValidAuth()) {
|
||||
const authSuccess = await applyAuth(page);
|
||||
|
||||
if (authSuccess) {
|
||||
// Verify login was successful by visiting a protected page
|
||||
await page.goto('http://localhost:3000/app/home');
|
||||
|
||||
// If we're still on the login page, we need to login again
|
||||
if (page.url().includes('/auth/login')) {
|
||||
await login(page);
|
||||
}
|
||||
} else {
|
||||
await login(page);
|
||||
}
|
||||
} else {
|
||||
await login(page);
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
export default globalSetup;
|
|
@ -0,0 +1,9 @@
|
|||
import { FullConfig } from '@playwright/test';
|
||||
|
||||
async function globalTeardown(config: FullConfig) {
|
||||
// Add any cleanup operations here if needed
|
||||
// For example, you might want to perform some API calls to reset test data
|
||||
console.log('Global teardown completed');
|
||||
}
|
||||
|
||||
export default globalTeardown;
|
|
@ -1,18 +0,0 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('has title', async ({ page }) => {
|
||||
await page.goto('https://playwright.dev/');
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/Playwright/);
|
||||
});
|
||||
|
||||
test('get started link', async ({ page }) => {
|
||||
await page.goto('https://playwright.dev/');
|
||||
|
||||
// Click the get started link.
|
||||
await page.getByRole('link', { name: 'Get started' }).click();
|
||||
|
||||
// Expects page to have a heading with the name of Installation.
|
||||
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('Login to buster', async ({ page }) => {
|
||||
//await page.getByText('Sign in').click();
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
await page.getByText('Sign in').click();
|
||||
await page.getByRole('textbox', { name: 'What is your email address?' }).fill('chad@buster.so');
|
||||
await page.getByRole('textbox', { name: 'What is your email address?' }).press('Tab');
|
||||
await page.getByRole('textbox', { name: 'Password' }).fill('password');
|
||||
await page.getByRole('button', { name: 'Sign in' }).click();
|
||||
await page.goto('http://localhost:3000/app/home');
|
||||
expect(page).toHaveURL('http://localhost:3000/app/home');
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('test', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000/auth/login');
|
||||
await expect(page.getByRole('button', { name: 'Sign up with Google' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Sign up with Github' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Sign up with Azure' })).toBeVisible();
|
||||
await expect(page.locator('body')).toContainText('Sign in');
|
||||
await expect(page.locator('body')).toMatchAriaSnapshot(`
|
||||
- heading "Sign up for free" [level=1]
|
||||
- button "Sign up with Google":
|
||||
- img
|
||||
- button "Sign up with Github":
|
||||
- img
|
||||
- button "Sign up with Azure":
|
||||
- img
|
||||
- textbox "What is your email address?"
|
||||
- textbox "Password"
|
||||
- textbox "Confirm password"
|
||||
- button "Sign up" [disabled]
|
||||
- text: Already have an account? Sign in
|
||||
`);
|
||||
});
|
|
@ -1,4 +1,6 @@
|
|||
import { defineConfig, devices } from '@playwright/test';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
|
@ -31,9 +33,19 @@ export default defineConfig({
|
|||
/* Capture screenshot on failure */
|
||||
screenshot: 'on',
|
||||
/* Run tests in headed mode (non-headless) */
|
||||
headless: false
|
||||
headless: false,
|
||||
/* Use stored auth state only if it exists */
|
||||
storageState: fs.existsSync(path.join(__dirname, 'playwright-tests/auth-utils/auth.json'))
|
||||
? path.join(__dirname, 'playwright-tests/auth-utils/auth.json')
|
||||
: undefined
|
||||
},
|
||||
|
||||
/* Global setup to run before all tests */
|
||||
globalSetup: './playwright-tests/auth-utils/global-setup.ts',
|
||||
|
||||
/* Global teardown to run after all tests */
|
||||
globalTeardown: './playwright-tests/auth-utils/global-teardown.ts',
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
|
@ -77,6 +89,6 @@ export default defineConfig({
|
|||
command: 'npm run dev',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120 * 1000 // 120 seconds
|
||||
timeout: 30 * 1000 // 30 seconds
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue