Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add e2e tests#331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
Naseem77 wants to merge18 commits intostaging
base:staging
Choose a base branch
Loading
fromadd-e2e-tests
Open
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
18 commits
Select commitHold shift + click to select a range
4906b4c
Add e2e Tests
Naseem77Dec 10, 2025
1238be6
Add api calls/responses for logic layer
Naseem77Dec 10, 2025
d96ac6b
adding pom for homePage
Naseem77Dec 10, 2025
7b40625
adding auth setup
Naseem77Dec 10, 2025
783d9f4
add docker compose file for database connections
Naseem77Dec 15, 2025
862a87b
add sidebar tests
Naseem77Dec 15, 2025
692efed
add user profile tests
Naseem77Dec 15, 2025
4d34598
update user profile tests
Naseem77Dec 15, 2025
a966b03
update auth setup - adding another user
Naseem77Dec 15, 2025
1b880a4
add database tests
Naseem77Dec 16, 2025
f9e369c
update playwright CI
Naseem77Dec 16, 2025
c124c2e
update ci
Naseem77Dec 16, 2025
f6c751f
update ci
Naseem77Dec 16, 2025
2ddeb39
Update playwright.yml
Naseem77Dec 16, 2025
2e65792
update ci
Naseem77Dec 16, 2025
d7233b7
update ci
Naseem77Dec 16, 2025
bfa04c1
Update playwright.yml
Naseem77Dec 16, 2025
610c3f5
update CI and tests
Naseem77Dec 16, 2025
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
NextNext commit
Add e2e Tests
Initialize Playwright e2e test architecture
  • Loading branch information
@Naseem77
Naseem77 committedDec 10, 2025
commit4906b4cc17bd05876c714a5dd51c3a0ee75e1b78

Some comments aren't visible on the classic Files Changed page.

27 changes: 27 additions & 0 deletions.github/workflows/playwright.yml
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, staging ]
pull_request:
branches: [ main, staging ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests

Choose a reason for hiding this comment

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

[blocker]: Playwright CI never launches backend. The Playwright workflow installs dependencies and immediately runsnpx playwright test, but it neither starts the QueryWeaver backend/frontend nor setsPLAYWRIGHT_BASE_URL, so every navigation/API helper will hit an empty localhost port and time out on CI. Add a step (or PlaywrightwebServer config) that starts the API/frontend, waits for it to become reachable, and exportsPLAYWRIGHT_BASE_URL before the tests run.

Choose a reason for hiding this comment

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

[blocker] This job installs deps and immediately executesnpx playwright test, but it never starts the QueryWeaver API/UI (or configures Playwright’swebServer). With no server listening onhttp://localhost:5000 every navigation/auth request in the suite will hitECONNREFUSED on CI. Please add a step (orwebServer config) that boots the backend and waits for it to be ready before running the tests so the workflow can actually exercise the app.

run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
6 changes: 6 additions & 0 deletions.gitignore
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,3 +25,9 @@ node_modules/
.jinja_cache/
demo_tokens.py
.DS_Store

# Playwright
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
50 changes: 50 additions & 0 deletionse2e/infra/api/apiRequests.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { APIRequestContext, request } from "@playwright/test"


const getRequest = async (url: string, headers?: Record<string, string>, body?: any, availableRequest?: APIRequestContext) => {

Choose a reason for hiding this comment

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

[major]: API request contexts leak. Each helper creates arequest.newContext() when one isn’t supplied but never disposes it, so repeated calls leak HTTP sessions/processes and eventually hang the runner. Track whether the helper created the context (e.g.,const shouldDispose = !availableRequest) andawait requestContext.dispose() in a finally block when true.

const requestOptions = {
data: body,
headers: headers || undefined,
};

const requestContext = availableRequest || (await request.newContext());

Choose a reason for hiding this comment

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

[major] Each helper here spins up a freshAPIRequestContext when one isn’t provided, but none of them ever dispose it. In a longer suite these stacks of orphaned contexts leak HTTP sessions/processes until Playwright hangs or hits the browser limit. Please track whether the helper created the context (e.g.const shouldDispose = !availableRequest) and callawait requestContext.dispose() in afinally block when appropriate so we don’t leak resources between API calls.

const response = await requestContext.get(url, requestOptions);
return response;
};

const postRequest = async (url: string, body?: any, availableRequest?: APIRequestContext, headers?: Record<string, string>) => {
const requestOptions = {
data: body,
headers: headers || undefined,
};

const requestContext = availableRequest || (await request.newContext());
const response = await requestContext.post(url, requestOptions);
return response;
};

const deleteRequest = async (url: string, headers?: Record<string, string>, body?: any) => {

Choose a reason for hiding this comment

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

[major]: DELETE helpers drop authentication.deleteRequest always creates a brand-newAPIRequestContext, sodeleteGraph/deleteToken lose the cookies/headers established by login and the backend rejects the call. Allow callers to pass an existing request context (like the GET/POST helpers do) so authenticated state is preserved for DELETE operations.

const requestOptions = {
data: body,
headers: headers || undefined,
};

const requestContext = await request.newContext();

Choose a reason for hiding this comment

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

[major] Unlike the GET/POST/PATCH helpers,deleteRequest always creates a brand‑newAPIRequestContext, so callers such asdeleteGraph/deleteToken drop the authenticated session they just established and hit the endpoint without cookies/headers. Please accept the optionalavailableRequest parameter (mirroring the other helpers) and reuse it so DELETE calls keep the same auth context as the rest of the API interactions.

const response = await requestContext.delete(url, requestOptions);
return response;
};

const patchRequest = async (url: string, body?: any, availableRequest?: APIRequestContext, headers?: Record<string, string>) => {
const requestOptions = {
data: body,
headers: headers || undefined,
};

const requestContext = availableRequest || (await request.newContext());
const response = await requestContext.patch(url, requestOptions);
return response;
};

export { getRequest, deleteRequest, postRequest, patchRequest }
30 changes: 30 additions & 0 deletionse2e/infra/ui/basePage.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
import { Page } from "playwright";

export default class BasePage {
protected page: Page;

constructor(page: Page) {
this.page = page;
}

async initPage() {
await this.page.waitForLoadState();
}

getCurrentURL(): string {
return this.page.url();
}

async refreshPage() {
await this.page.reload({ waitUntil: "networkidle" });
}

async waitForResponse(url: string) {
const response = await this.page.waitForResponse(url);
return response;
}

async waitForUrl(url: string) {
return this.page.waitForURL(url);
}
}
82 changes: 82 additions & 0 deletionse2e/infra/ui/browserWrapper.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
import { chromium, Browser, BrowserContext, Page, firefox } from 'playwright';
import { test } from '@playwright/test';
import BasePage from './basePage';

async function launchBrowser(projectName: string): Promise<Browser> {
if (projectName.toLowerCase().includes('firefox')) {
return firefox.launch();
}

return chromium.launch();
}

export default class BrowserWrapper {

private browser: Browser | null = null;

private context: BrowserContext | null = null;

private page: Page | null = null;

async createNewPage<T extends BasePage>(PageClass: new (page: Page) => T, url?: string) {
if (!this.browser) {
const projectName = test.info().project.name;
this.browser = await launchBrowser(projectName);
}
if (!this.context) {
this.context = await this.browser.newContext();
}
if (!this.page) {
this.page = await this.context.newPage();
}
if (url) {
await this.navigateTo(url)
}

const pageInstance = new PageClass(this.page);
return pageInstance;
}

getContext(): BrowserContext | null {
return this.context;
}

async getPage() {
if (!this.page) {
throw new Error('Browser is not launched yet!');
}
return this.page;
}

async setPageToFullScreen() {
if (!this.page) {
throw new Error('Browser is not launched yet!');
}

await this.page.setViewportSize({ width: 1920, height: 1080 });
}

async navigateTo(url: string) {
if (!this.page) {
throw new Error('Browser is not launched yet!');
}
await this.page.goto(url);
await this.page.waitForLoadState('networkidle');
}

async closePage() {
if (this.page) {
await this.page.close();
this.page = null;
}
}

async closeBrowser() {

Choose a reason for hiding this comment

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

[major]: Wrapper reuses handles after closing.closeBrowser() closes the page/browser but leavesthis.browser andthis.context populated, so the nextcreateNewPage() reuses handles Playwright has already torn down and throws target-closed errors. After closing, dispose the context and set boththis.browser andthis.context to null (or recreate them) so later calls relaunch cleanly.

Choose a reason for hiding this comment

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

[major]closeBrowser() only closes the page and then the raw browser handle, but it never closes the activeBrowserContext or resetthis.browser/this.context tonull. After one test finishes those fields keep pointing at Playwright objects that have already been torn down, so the nextcreateNewPage will try to reuse a dead context and throwbrowserContext.newPage: Protocol error: Target closed. Pleaseawait this.context?.close() and null the fields (browser/context/page) when shutting down so a subsequent run can relaunch cleanly.

await this.closePage();

if (this.browser) {
await this.browser.close();
}
}

}
67 changes: 67 additions & 0 deletionse2e/infra/utils.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
import { Locator, Page } from "playwright";

export function delay(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

export const waitForElementToBeVisible = async (
locator: Locator,
time = 500,
retry = 10
): Promise<boolean> => {
for (let i = 0; i < retry; i += 1) {
try {
if (await locator.isVisible()) {
return true;
}
} catch (error) {
console.error(`Error checking element visibility: ${error}`);
}
await delay(time);
}
return false;
};

export const waitForElementToNotBeVisible = async (
locator: Locator,
time = 500,
retry = 10
): Promise<boolean> => {
for (let i = 0; i < retry; i += 1) {
try {
if (!(await locator.isVisible())) {
return true;
}
} catch (error) {
console.error(`Error checking element visibility: ${error}`);
}
await delay(time);
}
return false;
};

export const waitForElementToBeEnabled = async (
locator: Locator,
time = 500,
retry = 10
): Promise<boolean> => {
for (let i = 0; i < retry; i += 1) {
try {
if (await locator.isEnabled()) {
return true;
}
} catch (error) {
console.error(`Error checking element enabled: ${error}`);
}
await delay(time);
}
return false;
};

export function getRandomString(prefix = "", delimiter = "_"): string {
const uuid = crypto.randomUUID();
const timestamp = Date.now();
return `${prefix}${prefix ? delimiter : ""}${uuid}-${timestamp}`;
}
Empty file addede2e/logic/api/apiCalls.ts
View file
Open in desktop
Empty file.
View file
Open in desktop
Empty file.
Empty file addede2e/logic/pom/homePage.ts
View file
Open in desktop
Empty file.
Loading

[8]ページ先頭

©2009-2025 Movatter.jp