Skip to content
Open
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
13 changes: 9 additions & 4 deletions e2e/createHeuristicTest.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { test, expect } from '@playwright/test';

// --- Reusable login function ---
const logIn = async (page) => {
const logIn = async (page, testInfo) => {
console.log('Base URL:', testInfo.project.use.baseURL);
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new console.log('Base URL:', ...) will add noise to test output and can leak environment details in CI logs. Consider removing it or gating it behind an explicit debug flag (e.g., an env var) so it’s only printed when troubleshooting.

Suggested change
console.log('Base URL:', testInfo.project.use.baseURL);
if (process.env.E2E_DEBUG_BASE_URL === 'true') {
console.log('Base URL:', testInfo.project.use.baseURL);
}

Copilot uses AI. Check for mistakes.
await test.step('Navigate to signin page', async () => {
await page.goto('http://localhost:8080/signin', { waitUntil: 'networkidle' });
// Use 'domcontentloaded' instead of 'networkidle' — Firebase keeps persistent
// WebSocket connections open so 'networkidle' never fires on this app.
await page.goto('/signin', { waitUntil: 'domcontentloaded' });
// Wait for the sign-in form to be interactive before proceeding.
await page.getByLabel('Email').waitFor({ state: 'visible', timeout: 15000 });
});

await test.step('Fill login credentials', async () => {
Expand Down Expand Up @@ -39,8 +44,8 @@ const fillTestForm = async (page, testName = '', testDescription = '', isPublic
};

test.describe('Heuristic Test Case: Creation Scenarios', () => {
test.beforeEach(async ({ page }) => {
await logIn(page);
test.beforeEach(async ({ page }, testInfo) => {
await logIn(page, testInfo);
});

test('Create test successfully', async ({ page }) => {
Expand Down
2 changes: 1 addition & 1 deletion e2e/ruxailabtest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ test.describe('Link Page Tests', () => {
try {

await test.step('Navigate to signin page', async () => {
await page.goto('http://localhost:8080/signin', {
await page.goto('/signin', {
waitUntil: 'networkidle',
timeout: 45000
});
Expand Down
8 changes: 4 additions & 4 deletions e2e/signIntest.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { test, expect } = require('@playwright/test');

// 🔄 Reusable login function
const logIn = async (page, email, password) => {
await page.goto('http://localhost:8080/signin');
await page.goto('/signin');
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
await page.getByTestId('sign-in-button').click();
Expand All @@ -22,7 +22,7 @@ test.describe('Sign In Workflow', () => {
try {
// 1. Initial access to Sign In page
await test.step('Navigate to Sign In page', async () => {
await page.goto('http://localhost:8080/signin', { waitUntil: 'networkidle' });
await page.goto('/signin', { waitUntil: 'networkidle' });
await expect(page.locator('#app')).toBeVisible();
await expect(page).toHaveTitle(/RUXAILAB/);
});
Expand Down Expand Up @@ -82,7 +82,7 @@ test.describe('Sign In Workflow', () => {

test('Password recovery only sends reset request', async ({ page }) => {
// 1) Go to Sign In page
await page.goto('http://localhost:8080/signin', { waitUntil: 'networkidle' });
await page.goto('/signin', { waitUntil: 'networkidle' });
await expect(page.locator('#app')).toBeVisible();

// 2) Click "Forgot Password" link (exact text from UI)
Expand Down Expand Up @@ -116,7 +116,7 @@ test.describe('Sign In Workflow', () => {

test('Validates password strength requirements during sign in', async ({ page }) => {
await test.step('Navigate to Sign In page', async () => {
await page.goto('http://localhost:8080/signin', { waitUntil: 'networkidle' });
await page.goto('/signin', { waitUntil: 'networkidle' });
await expect(page.locator('#app')).toBeVisible();
});

Expand Down
143 changes: 62 additions & 81 deletions e2e/template_testing.spec.ts
Original file line number Diff line number Diff line change
@@ -1,97 +1,78 @@
import { test, expect } from '@playwright/test';
import { test, expect, Page } from '@playwright/test';

test('should create a template and verify it appears in the list', async ({ page }) => {
await page.goto('http://localhost:8080/');
await page.getByRole('button', { name: 'Sign in' }).click();
/** Sign in with the shared test account. */
async function login(page: Page): Promise<void> {
await page.goto('/signin');
await page.getByLabel('Email').fill('dfa@dfa.com');
await page.getByLabel('Password', { exact: true }).fill('Password@123');
await page.getByTestId('sign-in-button').click();
// Removed "Go to Console" lines
await page.getByRole('tab', { name: 'Templates' }).click();
await page.getByRole('tab', { name: 'Personal' }).click();
await page.getByTestId('create-test-btn').click();
await page.getByText('Create a blank test', { exact: true }).click();
await page.getByText('Testing', { exact: true }).click();
await page.getByText('Webcam, audio & screen record').click();
await page.getByLabel('Test Name').click();
await page.getByLabel('Test Name').fill('test');
await page.getByLabel('Test Description').click();
await page.getByLabel('Test Description').fill('test');
await page.getByRole('dialog').getByRole('button').nth(1).click();
await page.getByText('Unmoderated').click();
await page.locator('div:nth-child(16) > .v-list-item__icon > .v-icon').click();
await page.locator('.dataCard > div > button').click();
await page.getByRole('button', { name: 'Return to Console' }).click();
await page.getByRole('button', { name: 'buttons.saveandleave' }).nth(1).click();
await page.getByRole('tab', { name: 'Templates' }).click();
});
}

test('should show error when creating a template with duplicate name', async ({ page }) => {
await page.goto('http://localhost:8080/');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByLabel('Email').fill('dfa@dfa.com');
await page.getByLabel('Password', { exact: true }).fill('Password@123');
await page.getByTestId('sign-in-button').click();
/** Navigate to the Personal templates tab (assumes already signed in). */
async function goToPersonalTemplates(page: Page): Promise<void> {
await page.getByRole('tab', { name: 'Templates' }).click();
await page.getByRole('tab', { name: 'Personal' }).click();
await page.getByTestId('create-test-btn').click();
await page.getByText('Create a blank test', { exact: true }).click();
await page.getByText('Testing', { exact: true }).click();
await page.getByText('Webcam, audio & screen record').click();
await page.getByLabel('Test Name').fill('test');
await page.getByLabel('Test Description').fill('test');
await page.getByRole('dialog').getByRole('button').nth(1).click();
// Expect error message for duplicate name
await expect(page.getByText(/already exists|duplicate/i)).toBeVisible();
});
}

test('should validate required fields when creating a template', async ({ page }) => {
await page.goto('http://localhost:8080/');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByLabel('Email').fill('dfa@dfa.com');
await page.getByLabel('Password', { exact: true }).fill('Password@123');
await page.getByTestId('sign-in-button').click();
await page.getByRole('tab', { name: 'Templates' }).click();
await page.getByRole('tab', { name: 'Personal' }).click();
/** Open the new-test dialog and select the User Test type (assumes on console page). */
async function openNewUserTestDialog(page: Page): Promise<void> {
await page.getByTestId('create-test-btn').click();
await page.getByText('Create a blank test', { exact: true }).click();
await page.getByText('Testing', { exact: true }).click();
await page.getByText('Webcam, audio & screen record').click();
// Leave Test Name empty
await page.getByLabel('Test Description').fill('test');
await page.getByRole('dialog').getByRole('button').nth(1).click();
await expect(page.getByText(/required|enter a name/i)).toBeVisible();
});
}

test('should filter templates by name', async ({ page }) => {
await page.goto('http://localhost:8080/');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByLabel('Email').fill('dfa@dfa.com');
await page.getByLabel('Password', { exact: true }).fill('Password@123');
await page.getByTestId('sign-in-button').click();
await page.getByRole('tab', { name: 'Templates' }).click();
await page.getByRole('tab', { name: 'Personal' }).click();
// Assume there is a search/filter input for templates
await page.getByPlaceholder('Search templates').fill('test');
await expect(page.getByText('test')).toBeVisible();
});
test.describe('Template management', () => {
test.beforeEach(async ({ page }) => {
await login(page);
});

test('should not allow saving template with empty description', async ({ page }) => {
await page.goto('http://localhost:8080/');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByLabel('Email').fill('dfa@dfa.com');
await page.getByLabel('Password', { exact: true }).fill('Password@123');
await page.getByTestId('sign-in-button').click();
await page.getByRole('tab', { name: 'Templates' }).click();
await page.getByRole('tab', { name: 'Personal' }).click();
await page.getByTestId('create-test-btn').click();
await page.getByText('Create a blank test', { exact: true }).click();
await page.getByText('Testing', { exact: true }).click();
await page.getByText('Webcam, audio & screen record').click();
await page.getByLabel('Test Name').fill('test-empty-desc');
// Leave Test Description empty
await page.getByRole('dialog').getByRole('button').nth(1).click();
await expect(page.getByText(/required|enter a description/i)).toBeVisible();
});
test('should create a template and verify it appears in the list', async ({ page }) => {
await goToPersonalTemplates(page);
await openNewUserTestDialog(page);
await page.getByLabel('Test Name').click();
await page.getByLabel('Test Name').fill('test');
await page.getByLabel('Test Description').click();
await page.getByLabel('Test Description').fill('test');
await page.getByRole('dialog').getByRole('button').nth(1).click();
await page.getByText('Unmoderated').click();
await page.locator('div:nth-child(16) > .v-list-item__icon > .v-icon').click();
await page.locator('.dataCard > div > button').click();
await page.getByRole('button', { name: 'Return to Console' }).click();
await page.getByRole('button', { name: 'buttons.saveandleave' }).nth(1).click();
await page.getByRole('tab', { name: 'Templates' }).click();
});

test('should show error when creating a template with duplicate name', async ({ page }) => {
await goToPersonalTemplates(page);
await openNewUserTestDialog(page);
await page.getByLabel('Test Name').fill('test');
await page.getByLabel('Test Description').fill('test');
await page.getByRole('dialog').getByRole('button').nth(1).click();
await expect(page.getByText(/already exists|duplicate/i)).toBeVisible();
});

test('should validate required fields when creating a template', async ({ page }) => {
await goToPersonalTemplates(page);
await openNewUserTestDialog(page);
// Leave Test Name empty
await page.getByLabel('Test Description').fill('test');
await page.getByRole('dialog').getByRole('button').nth(1).click();
await expect(page.getByText(/required|enter a name/i)).toBeVisible();
});

test('should filter templates by name', async ({ page }) => {
await goToPersonalTemplates(page);
await page.getByPlaceholder('Search templates').fill('test');
await expect(page.getByText('test')).toBeVisible();
});

test('should not allow saving template with empty description', async ({ page }) => {
await goToPersonalTemplates(page);
await openNewUserTestDialog(page);
await page.getByLabel('Test Name').fill('test-empty-desc');
// Leave Test Description empty
await page.getByRole('dialog').getByRole('button').nth(1).click();
await expect(page.getByText(/required|enter a description/i)).toBeVisible();
});
});
24 changes: 10 additions & 14 deletions playwright.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// @ts-check
const { defineConfig, devices } = require('@playwright/test');

const devBaseUrl = 'http://localhost:8080';

module.exports = defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
Expand All @@ -24,8 +22,10 @@ module.exports = defineConfig({
outputDir: './playwright/output',

use: {
baseURL: devBaseUrl,
...devices['Desktop Chrome'],
/* Base URL so relative paths like page.goto('/signin') resolve correctly.
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using process.env.BASE_URL here is easy to confuse with Vue CLI’s BASE_URL (often set to a public path like /). If that env var is present when running Playwright, it can produce an invalid baseURL. Consider switching to a more specific env var (e.g. PLAYWRIGHT_BASE_URL/E2E_BASE_URL) and/or validating that it’s a full origin URL (scheme + host).

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default fallback here is http://localhost:8080, but the repo’s Dockerfile-playwright serves the app on port 5000 and runs npx playwright test without setting a base URL. If that Docker workflow is expected to work, the fallback should align with the served port or the Docker command should export the env var used here.

Copilot uses AI. Check for mistakes.
Override by setting BASE_URL env var, e.g. BASE_URL=https://staging.example.com */
baseURL: process.env.BASE_URL || 'http://localhost:8080',
Comment on lines +25 to +27
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

playwright test is invoked without --config, and this repo contains both playwright.config.ts and playwright.config.js (Dockerfile-playwright copies both). If Playwright resolves the TS config by default, baseURL from this JS file won’t be applied and page.goto('/...') will fail. To avoid ambiguous/incorrect config selection, either move this baseURL setting into the config file that’s actually used (likely playwright.config.ts), or remove/rename the unused config so only one default config exists.

Suggested change
/* Base URL so relative paths like page.goto('/signin') resolve correctly.
Override by setting BASE_URL env var, e.g. BASE_URL=https://staging.example.com */
baseURL: process.env.BASE_URL || 'http://localhost:8080',

Copilot uses AI. Check for mistakes.

/* Collect trace when retrying the failed test */
trace: 'on-first-retry',

Expand All @@ -39,25 +39,21 @@ module.exports = defineConfig({
navigationTimeout: 30000,
},

/* Configure projects for major browsers */
/* Configure projects for major browsers.
Each project inherits the global `use` block (including baseURL) and adds
its own device-specific viewport / userAgent settings. */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
},
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
},
use: { ...devices['Desktop Safari'] },
},
],
});
90 changes: 28 additions & 62 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,47 @@
import { defineConfig, devices } from '@playwright/test'
import { defineConfig, devices, PlaywrightTestOptions } from '@playwright/test'
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PlaywrightTestOptions is only used as a type here, but it’s imported as a runtime value. Consider switching this to a type-only import (or using type PlaywrightTestOptions in the import) to avoid carrying an unnecessary runtime import and to prevent issues if the config is ever loaded in ESM mode.

Suggested change
import { defineConfig, devices, PlaywrightTestOptions } from '@playwright/test'
import { defineConfig, devices, type PlaywrightTestOptions } from '@playwright/test'

Copilot uses AI. Check for mistakes.

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
* Shared base options spread into the global use block and every project.
* Defined once here — the single source of truth that SonarCloud checks.
*
Comment on lines +4 to +6
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file’s header comment says it is the “single source of truth”, but the repo also maintains a parallel playwright.config.js with overlapping settings (including baseURL). That duplication makes it easy for the two configs to drift; consider consolidating into a single Playwright config or updating test scripts/CI to explicitly select one config via --config so there’s a clear source of truth.

Suggested change
* Shared base options spread into the global use block and every project.
* Defined once here the single source of truth that SonarCloud checks.
*
* Shared base options for this Playwright config, spread into the global
* use block and every project defined below.
*
* Keep this in sync with any other Playwright config files (for example,
* playwright.config.js) so that settings like baseURL do not drift.

Copilot uses AI. Check for mistakes.
* Override at runtime:
* BASE_URL=https://staging.example.com npx playwright test
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
const baseUse: Partial<PlaywrightTestOptions> = {
baseURL: process.env.BASE_URL || 'http://localhost:8080',
}

/**
* See https://playwright.dev/docs/test-configuration.
* Browser matrix: [projectName, deviceDescriptor]
* Add or remove rows here to change which browsers are tested — no repetition
* of the project-object shape needed.
*/
const browserProjects: [string, (typeof devices)[string]][] = [
['chromium', devices['Desktop Chrome']],
['firefox', devices['Desktop Firefox']],
['webkit', devices['Desktop Safari']],
]

/** See https://playwright.dev/docs/test-configuration. */
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */

/* Global settings inherited by all projects. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',

...baseUse,
video: 'on',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
/* Projects are generated from the matrix above — the use-block shape
is written once in the map callback rather than repeated per browser. */
projects: browserProjects.map(([name, device]) => ({
name,
use: { ...baseUse, ...device },
})),
})