chore: Janitor: deduplicate shared page-object selectors (project-name, menu-item) (#34390)

Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
n8n-cat-bot[bot] 2026-07-17 07:50:29 +01:00 committed by Matsuuu
parent e02d73aee5
commit 51e4a809f7
No known key found for this signature in database
7 changed files with 44 additions and 22 deletions

View File

@ -1,7 +1,7 @@
{
"version": 1,
"generated": "2026-07-15T03:17:41.383Z",
"totalViolations": 65,
"generated": "2026-07-17T06:17:55.330Z",
"totalViolations": 63,
"violations": {
"pages/AIAssistantPage.ts": [
{
@ -367,22 +367,6 @@
"hash": "974d7953dd83"
}
],
"pages/WorkflowsPage.ts": [
{
"rule": "deduplication",
"line": 40,
"message": "Duplicate locator: this.page.getByTestId(\"project-name\") in pages scope",
"hash": "206b89bd1594"
}
],
"pages/WorkerViewPage.ts": [
{
"rule": "deduplication",
"line": 13,
"message": "Duplicate locator: this.page.getByTestId(\"menu-item\") in pages scope",
"hash": "5a1818f5f511"
}
],
"tests/infrastructure/benchmarks/kafka/queue-mode-sustained-rate.spec.ts": [
{
"rule": "duplicate-logic",

View File

@ -2,8 +2,11 @@ import type { Locator } from '@playwright/test';
import { expect } from '@playwright/test';
import { BasePage } from './BasePage';
import { ProjectHeader } from './components/ProjectHeader';
export class ProjectSettingsPage extends BasePage {
readonly projectHeader = new ProjectHeader(this.page);
async goto(projectId: string) {
await this.page.goto(`/projects/${projectId}/settings`);
}
@ -93,7 +96,7 @@ export class ProjectSettingsPage extends BasePage {
}
getTitle() {
return this.page.getByTestId('project-name');
return this.projectHeader.getProjectName();
}
// Robust value assertions on inner form controls

View File

@ -1,13 +1,16 @@
import type { Locator } from '@playwright/test';
import { BasePage } from './BasePage';
import { SettingsSidebar } from './components/SettingsSidebar';
/**
* Page object for Settings including Personal Settings where users can update their profile and manage MFA.
*/
export class SettingsPersonalPage extends BasePage {
readonly settingsSidebar = new SettingsSidebar(this.page);
getMenuItems() {
return this.page.getByTestId('menu-item');
return this.settingsSidebar.getMenuItems();
}
async gotoSettings() {

View File

@ -1,6 +1,9 @@
import { BasePage } from './BasePage';
import { SettingsSidebar } from './components/SettingsSidebar';
export class WorkerViewPage extends BasePage {
readonly settingsSidebar = new SettingsSidebar(this.page);
getWorkerViewLicensed() {
return this.page.getByTestId('worker-view-licensed');
}
@ -10,7 +13,7 @@ export class WorkerViewPage extends BasePage {
}
getWorkerMenuItem() {
return this.page.getByTestId('menu-item').getByText('Workers', { exact: true });
return this.settingsSidebar.getMenuItems().getByText('Workers', { exact: true });
}
async goto() {

View File

@ -3,6 +3,7 @@ import type { Locator } from '@playwright/test';
import { BasePage } from './BasePage';
import { ActionToggle } from './components/ActionToggle';
import { AddResource } from './components/AddResource';
import { ProjectHeader } from './components/ProjectHeader';
import { ResourceCards } from './components/ResourceCards';
import { ResourceMoveModal } from './components/ResourceMoveModal';
@ -15,6 +16,7 @@ export class WorkflowsPage extends BasePage {
readonly cards = new ResourceCards(this.page);
readonly actionToggle = new ActionToggle(this.page);
readonly resourceMoveModal = new ResourceMoveModal(this.page);
readonly projectHeader = new ProjectHeader(this.page);
private async openWorkflowCardActions(workflowItem: Locator) {
await workflowItem.getByTestId('workflow-card-actions').getByRole('button').click();
@ -37,7 +39,7 @@ export class WorkflowsPage extends BasePage {
}
getProjectName() {
return this.page.getByTestId('project-name');
return this.projectHeader.getProjectName();
}
getSearchBar() {

View File

@ -0,0 +1,14 @@
import type { Page } from '@playwright/test';
/**
* ProjectHeader component - the project title/breadcrumb header shown at the top
* of project views (workflows list, project settings, etc.).
* Mirrors the ProjectHeader.vue component in the frontend.
*/
export class ProjectHeader {
constructor(private readonly page: Page) {}
getProjectName() {
return this.page.getByTestId('project-name');
}
}

View File

@ -0,0 +1,13 @@
import type { Page } from '@playwright/test';
/**
* SettingsSidebar component - the left-hand menu shared across the `/settings*`
* routes (personal, workers, etc.).
*/
export class SettingsSidebar {
constructor(private readonly page: Page) {}
getMenuItems() {
return this.page.getByTestId('menu-item');
}
}