diff --git a/tests/sanity/tests/model/profile/user-profile-page.ts b/tests/sanity/tests/model/profile/user-profile-page.ts index 178d2cddaf..1a4de9f121 100644 --- a/tests/sanity/tests/model/profile/user-profile-page.ts +++ b/tests/sanity/tests/model/profile/user-profile-page.ts @@ -13,7 +13,7 @@ export class UserProfilePage { selectProfile = (name: string): Locator => this.page.locator(`text=${name}`) leaveWorkspaceButton = (): Locator => this.page.getByRole('button', { name: 'Leave workspace' }) leaveWorkspaceCancelButton = (): Locator => this.page.getByRole('button', { name: 'Cancel' }) - leaveWorkspaceConfirmButton = (): Locator => this.page.getByRole('button', { name: 'Ok' }) + leaveWorkspaceConfirmButton = (): Locator => this.page.getByRole('button', { name: 'Ok', exact: true }) accountDissabledMessage = (): Locator => this.page.getByRole('heading') changeAccount = (): Locator => this.page.getByRole('link', { name: 'Change account' }) settings = (): Locator => this.page.getByRole('button', { name: 'Settings' }) diff --git a/tests/sanity/tests/model/tracker/all-projects-page.ts b/tests/sanity/tests/model/tracker/all-projects-page.ts new file mode 100644 index 0000000000..22b91a8a58 --- /dev/null +++ b/tests/sanity/tests/model/tracker/all-projects-page.ts @@ -0,0 +1,31 @@ +import { expect, type Locator } from '@playwright/test' +import { CommonTrackerPage } from './common-tracker-page' + +export class AllProjectsPage extends CommonTrackerPage { + projectTitleCells = (): Locator => this.page.locator('.antiTable-body__row .antiTable-cells__firstCell') + + async checkProjectExistInTable (projectName: string): Promise { + await expect(this.projectTitleCells().filter({ hasText: projectName })).toBeVisible() + } + + async checkProjectNotExistInTable (projectName: string): Promise { + await expect(this.projectTitleCells().filter({ hasText: projectName })).toBeHidden() + } + + async joinProject (title: string): Promise { + const projectRow = this.page.locator('.antiTable-body__row', { has: this.page.locator(`td:has-text("${title}")`) }) + const joinButton = projectRow.locator('button:has-text("Join")') + await joinButton.click() + } + + async unarchiveProject (title: string): Promise { + const projectRow = this.page.locator('.antiTable-body__row', { has: this.page.locator(`td:has-text("${title}")`) }) + await projectRow.click({ button: 'right' }) + await this.page.locator('.popup button:has-text("Unarchive")').click() + await this.page.locator('.popup button:has-text("Ok")').click() + } + + async checkProjecInTableIsNotArchived (title: string): Promise { + await this.projectTitleCells().filter({ hasText: title }).filter({ hasNotText: '(archived)' }).waitFor() + } +} diff --git a/tests/sanity/tests/model/tracker/issues-page.ts b/tests/sanity/tests/model/tracker/issues-page.ts index 8e4bc9f758..65a75c0089 100644 --- a/tests/sanity/tests/model/tracker/issues-page.ts +++ b/tests/sanity/tests/model/tracker/issues-page.ts @@ -150,7 +150,7 @@ export class IssuesPage extends CommonTrackerPage { this.page.locator('[id="tracker\\:string\\:TimeSpendReportAdd"] >> text=Add time report') estimationSpan = (): Locator => this.page.locator('.estimation-container >> span').first() - okButton = (): Locator => this.page.getByRole('button', { name: 'Ok' }) + okButton = (): Locator => this.page.getByRole('button', { name: 'Ok', exact: true }) newIssueButton = (): Locator => this.page.locator('#new-issue') issueNameInput = (): Locator => this.page.locator('#issue-name >> input') issueDescriptionInput = (): Locator => this.page.locator('#issue-description >> [contenteditable]') @@ -159,7 +159,7 @@ export class IssuesPage extends CommonTrackerPage { priorityEditor = (): Locator => this.page.locator('#priority-editor') urgentButton = (): Locator => this.page.locator('button:has-text("Urgent")') assigneeEditor = (): Locator => this.page.locator('#assignee-editor') - appleseedJohnButton = (): Locator => this.page.locator('button:has-text("Appleseed John")') + appleseedJohnButton = (): Locator => this.page.locator('button.menu-item:has-text("Appleseed John")') estimationEditor = (): Locator => this.page.locator('#estimation-editor') dueDateButton = (): Locator => this.page.locator('button:has-text("Due date")') specificDay = (day: string): Locator => this.page.locator(`.date-popup-container div.day >> text=${day}`).first() diff --git a/tests/sanity/tests/model/tracker/tracker-navigation-menu-page.ts b/tests/sanity/tests/model/tracker/tracker-navigation-menu-page.ts index 83aaa4c3e4..ec294fa092 100644 --- a/tests/sanity/tests/model/tracker/tracker-navigation-menu-page.ts +++ b/tests/sanity/tests/model/tracker/tracker-navigation-menu-page.ts @@ -9,11 +9,16 @@ export class TrackerNavigationMenuPage extends CommonPage { this.page = page } - buttonCreateProject = (): Locator => this.page.locator('div#navGroup-tree-projects').locator('xpath=../button[1]') + yoursProjectsMenuSelector = '#navGroup-tree-projects' + starredProjectsMenuSelector = '#navGroup-tree-stared' + + buttonCreateProject = (): Locator => this.page.locator(this.yoursProjectsMenuSelector).locator('xpath=../button[1]') buttonProjectsParent = (): Locator => this.page.locator('button.hulyNavGroup-header span') templateLinkForProject = (projectName: string): Locator => this.page.locator(`a[href$="templates"][href*="${projectName}"]`) + starredProjectsInMenu = (): Locator => this.page.locator(`${this.starredProjectsMenuSelector} span`) + issuesLinkForProject = (projectName: string): Locator => this.page .getByRole('button', { name: projectName, exact: true }) @@ -49,6 +54,22 @@ export class TrackerNavigationMenuPage extends CommonPage { await expect(this.buttonProjectsParent().filter({ hasText: projectName })).toHaveCount(1) } + async checkProjectStarred (projectName: string): Promise { + await expect(this.starredProjectsInMenu().filter({ hasText: projectName })).toHaveCount(1) + } + + async checkProjectWillBeRemovedFromYours (projectName: string): Promise { + await this.page.waitForSelector(`${this.yoursProjectsMenuSelector} span:has-text("${projectName}")`, { + state: 'detached' + }) + } + + async checkProjectWillBeRemovedFromStarred (projectName: string): Promise { + await this.page.waitForSelector(`${this.starredProjectsMenuSelector} span:has-text("${projectName}")`, { + state: 'detached' + }) + } + async checkProjectNotExist (projectName: string): Promise { await expect(this.buttonProjectsParent().filter({ hasText: projectName })).toHaveCount(0) } @@ -75,6 +96,16 @@ export class TrackerNavigationMenuPage extends CommonPage { await this.selectFromDropdown(this.page, action) } + async makeActionWithStarredProject (projectName: string, action: string): Promise { + await this.starredProjectsInMenu().filter({ hasText: projectName }).hover() + await this.starredProjectsInMenu() + .filter({ hasText: projectName }) + .locator('xpath=../..') + .locator('div[class*="tools"] button') + .click() + await this.selectFromDropdown(this.page, action) + } + async openMilestonesForProject (projectName: string): Promise { await this.milestonesLinkForProject(projectName).click() } @@ -96,4 +127,8 @@ export class TrackerNavigationMenuPage extends CommonPage { await expect(this.milestone()).toBeVisible() await expect(this.templates()).toBeVisible() } + + async openAllProjects (): Promise { + await this.allProjects().click() + } } diff --git a/tests/sanity/tests/tracker/common-steps.ts b/tests/sanity/tests/tracker/common-steps.ts index 097f960510..786397bf20 100644 --- a/tests/sanity/tests/tracker/common-steps.ts +++ b/tests/sanity/tests/tracker/common-steps.ts @@ -15,7 +15,7 @@ export async function prepareNewIssueStep (page: Page, issue: NewIssue): Promise } export async function prepareNewIssueWithOpenStep (page: Page, issue: NewIssue): Promise { - return await test.step('Prepare document', async () => { + return await test.step('Prepare Issue', async () => { const issuesPage = new IssuesPage(page) await issuesPage.linkSidebarAll().click() await issuesPage.clickModelSelectorAll() diff --git a/tests/sanity/tests/tracker/issues.spec.ts b/tests/sanity/tests/tracker/issues.spec.ts index fb0c445f07..1b40edac56 100644 --- a/tests/sanity/tests/tracker/issues.spec.ts +++ b/tests/sanity/tests/tracker/issues.spec.ts @@ -174,6 +174,8 @@ test.describe('Tracker issue tests', () => { await issuesDetailsPage.checkIssue({ ...moveIssue }) + + await trackerNavigationMenuPage.openIssuesForProject('Default') // TODO need to return back after bug with activity fixed // await issuesDetailsPage.checkActivityExist('changed project in') // await issuesDetailsPage.checkActivityExist('changed number in') @@ -221,9 +223,11 @@ test.describe('Tracker issue tests', () => { test('Delete an issue', async ({ page }) => { const deleteIssue: NewIssue = { - title: 'Issue for deletion', + title: `Issue-to-delete-${generateId()}`, description: 'Description Issue for deletion' } + await prepareNewIssueWithOpenStep(page, deleteIssue) + await issuesPage.navigateToIssues() await issuesPage.clickModelSelectorAll() await issuesPage.searchIssueByName(deleteIssue.title) await issuesPage.openIssueByName(deleteIssue.title) diff --git a/tests/sanity/tests/tracker/projects.spec.ts b/tests/sanity/tests/tracker/projects.spec.ts index 8002c9c70d..2e3eb3a4f2 100644 --- a/tests/sanity/tests/tracker/projects.spec.ts +++ b/tests/sanity/tests/tracker/projects.spec.ts @@ -4,6 +4,8 @@ import { TrackerNavigationMenuPage } from '../model/tracker/tracker-navigation-m import { NewProjectPage } from '../model/tracker/new-project-page' import { NewProject } from '../model/tracker/types' import { EditProjectPage } from '../model/tracker/edit-project-page' +import { AllProjectsPage } from '../model/tracker/all-projects-page' +import { generateProjectId } from './tracker.utils' test.use({ storageState: PlatformSetting @@ -13,76 +15,189 @@ test.describe('Tracker Projects tests', () => { let trackerNavigationMenuPage: TrackerNavigationMenuPage let newProjectPage: NewProjectPage let editProjectPage: EditProjectPage + let allProjectsPage: AllProjectsPage test.beforeEach(async ({ page }) => { trackerNavigationMenuPage = new TrackerNavigationMenuPage(page) newProjectPage = new NewProjectPage(page) editProjectPage = new EditProjectPage(page) + allProjectsPage = new AllProjectsPage(page) await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() }) - test('Create project', async () => { + test('User can create project', async () => { + const projectId = generateProjectId() const newProjectData: NewProject = { - title: 'TestProject', - identifier: 'QWERT', - description: 'Test Project description', + title: `NewProject-${projectId}`, + identifier: projectId, + description: 'New Project description', private: true, defaultAssigneeForIssues: 'Dirak Kainin', defaultIssueStatus: 'In Progress' } - await trackerNavigationMenuPage.checkProjectNotExist(newProjectData.title) - await trackerNavigationMenuPage.pressCreateProjectButton() - await newProjectPage.createNewProject(newProjectData) - await trackerNavigationMenuPage.checkProjectExist(newProjectData.title) - await trackerNavigationMenuPage.openProject(newProjectData.title) + await test.step('User create project', async () => { + await trackerNavigationMenuPage.checkProjectNotExist(newProjectData.title) + await trackerNavigationMenuPage.pressCreateProjectButton() + await newProjectPage.createNewProject(newProjectData) + }) + + await test.step('User see project in menu', async () => { + await trackerNavigationMenuPage.checkProjectExist(newProjectData.title) + }) + + await test.step('User see project in all projects table', async () => { + await trackerNavigationMenuPage.openAllProjects() + await allProjectsPage.checkProjectExistInTable(newProjectData.title) + }) + + await test.step('User can open created project', async () => { + await trackerNavigationMenuPage.openProject(newProjectData.title) + }) }) - test('Edit project', async () => { + test('User can edit project', async () => { + const projectId = generateProjectId() + const editProjectData: NewProject = { - title: 'EditProject', - identifier: 'EDIT', + title: `EditProject-${projectId}`, + identifier: projectId, description: 'Edit Project description', private: true, defaultAssigneeForIssues: 'Dirak Kainin', defaultIssueStatus: 'In Progress' } const updateProjectData: NewProject = { - title: 'UpdateProject', - identifier: 'EDIT', + title: `UpdateProject-${projectId}`, + identifier: projectId, description: 'Updated Project description', private: true, defaultAssigneeForIssues: 'Chen Rosamund', defaultIssueStatus: 'Done' } - await trackerNavigationMenuPage.checkProjectNotExist(editProjectData.title) - await trackerNavigationMenuPage.pressCreateProjectButton() - await newProjectPage.createNewProject(editProjectData) - await trackerNavigationMenuPage.checkProjectExist(editProjectData.title) - await trackerNavigationMenuPage.makeActionWithProject(editProjectData.title, 'Edit project') - await editProjectPage.checkProject(editProjectData) - await editProjectPage.updateProject(updateProjectData) - await trackerNavigationMenuPage.makeActionWithProject(updateProjectData.title, 'Edit project') - await editProjectPage.checkProject(updateProjectData) + await test.step('User prepare project', async () => { + await trackerNavigationMenuPage.checkProjectNotExist(editProjectData.title) + await trackerNavigationMenuPage.pressCreateProjectButton() + await newProjectPage.createNewProject(editProjectData) + await trackerNavigationMenuPage.checkProjectExist(editProjectData.title) + }) + + await test.step('User edit project', async () => { + await trackerNavigationMenuPage.makeActionWithProject(editProjectData.title, 'Edit project') + await editProjectPage.checkProject(editProjectData) + await editProjectPage.updateProject(updateProjectData) + await trackerNavigationMenuPage.makeActionWithProject(updateProjectData.title, 'Edit project') + await editProjectPage.checkProject(updateProjectData) + await editProjectPage.buttonSaveProject().click() + }) }) - test('Archive Project', async ({ page }) => { + test('User can archive and unarchive Project', async ({ page }) => { + const projectId = generateProjectId() + const archiveProjectData: NewProject = { - title: 'PROJECT_ARCHIVE', - identifier: 'ARCH', + title: `ArchiveProject-${projectId}`, + identifier: projectId, description: 'Archive Project description', private: true, defaultAssigneeForIssues: 'Dirak Kainin', defaultIssueStatus: 'In Progress' } - await trackerNavigationMenuPage.checkProjectNotExist(archiveProjectData.title) - await trackerNavigationMenuPage.pressCreateProjectButton() - await newProjectPage.createNewProject(archiveProjectData) - await trackerNavigationMenuPage.checkProjectExist(archiveProjectData.title) - await trackerNavigationMenuPage.makeActionWithProject(archiveProjectData.title, 'Archive') - await trackerNavigationMenuPage.pressYesForPopup(page) - await trackerNavigationMenuPage.checkProjectNotExist(archiveProjectData.title) + await test.step('User prepare project', async () => { + await trackerNavigationMenuPage.checkProjectNotExist(archiveProjectData.title) + await trackerNavigationMenuPage.pressCreateProjectButton() + await newProjectPage.createNewProject(archiveProjectData) + }) + + await test.step('User archive project', async () => { + await trackerNavigationMenuPage.checkProjectExist(archiveProjectData.title) + await trackerNavigationMenuPage.makeActionWithProject(archiveProjectData.title, 'Archive') + await trackerNavigationMenuPage.pressYesForPopup(page) + await trackerNavigationMenuPage.checkProjectNotExist(archiveProjectData.title) + await trackerNavigationMenuPage.openAllProjects() + await allProjectsPage.checkProjectExistInTable(`${archiveProjectData.title} (archived)`) + }) + + await test.step('User unarchive project', async () => { + await allProjectsPage.unarchiveProject(archiveProjectData.title) + await allProjectsPage.checkProjecInTableIsNotArchived(`${archiveProjectData.title}`) + await trackerNavigationMenuPage.checkProjectExist(archiveProjectData.title) + }) + + await test.step('Finally archive project again', async () => { + await trackerNavigationMenuPage.makeActionWithProject(archiveProjectData.title, 'Archive') + await trackerNavigationMenuPage.pressYesForPopup(page) + await trackerNavigationMenuPage.checkProjectNotExist(archiveProjectData.title) + }) + }) + + test('Star and Unstar Project', async () => { + const projectId = generateProjectId() + + const projectToStarData: NewProject = { + title: `ProjectToStar-${projectId}`, + identifier: projectId, + description: 'Starred Project description', + private: true, + defaultAssigneeForIssues: 'Dirak Kainin', + defaultIssueStatus: 'In Progress' + } + + await test.step('User prepare project', async () => { + await trackerNavigationMenuPage.checkProjectNotExist(projectToStarData.title) + await trackerNavigationMenuPage.pressCreateProjectButton() + await newProjectPage.createNewProject(projectToStarData) + await trackerNavigationMenuPage.checkProjectExist(projectToStarData.title) + }) + + await test.step('User star project', async () => { + await trackerNavigationMenuPage.makeActionWithProject(projectToStarData.title, 'Star') + await trackerNavigationMenuPage.checkProjectStarred(projectToStarData.title) + await trackerNavigationMenuPage.checkProjectWillBeRemovedFromYours(projectToStarData.title) + }) + + await test.step('User unstar project', async () => { + await trackerNavigationMenuPage.makeActionWithStarredProject(projectToStarData.title, 'Unstar') + await trackerNavigationMenuPage.checkProjectExist(projectToStarData.title) + await trackerNavigationMenuPage.checkProjectWillBeRemovedFromStarred(projectToStarData.title) + }) + }) + + test('Leave and Join Project', async () => { + const projectId = generateProjectId() + + const projectToLeaveData: NewProject = { + title: `ProjectToLeave-${projectId}`, + identifier: projectId, + description: 'Project to leave description', + private: true, + defaultAssigneeForIssues: 'Appleseed John', + defaultIssueStatus: 'In Progress' + } + + await test.step('User prepare project', async () => { + await trackerNavigationMenuPage.checkProjectNotExist(projectToLeaveData.title) + await trackerNavigationMenuPage.pressCreateProjectButton() + await newProjectPage.createNewProject(projectToLeaveData) + await trackerNavigationMenuPage.checkProjectExist(projectToLeaveData.title) + }) + + await test.step('User leave project', async () => { + await trackerNavigationMenuPage.makeActionWithProject(projectToLeaveData.title, 'Leave') + await trackerNavigationMenuPage.checkProjectWillBeRemovedFromYours(projectToLeaveData.title) + }) + + await test.step('User join project', async () => { + await trackerNavigationMenuPage.openAllProjects() + await allProjectsPage.checkProjectExistInTable(projectToLeaveData.title) + await allProjectsPage.joinProject(projectToLeaveData.title) + await trackerNavigationMenuPage.checkProjectExist(projectToLeaveData.title) + }) + }) + + test.afterEach(async () => { + await trackerNavigationMenuPage.openIssuesForProject('Default') }) }) diff --git a/tests/sanity/tests/tracker/tracker.utils.ts b/tests/sanity/tests/tracker/tracker.utils.ts index 2cad93305e..0098d86671 100644 --- a/tests/sanity/tests/tracker/tracker.utils.ts +++ b/tests/sanity/tests/tracker/tracker.utils.ts @@ -253,6 +253,16 @@ export async function toTime (value: number): Promise { ].join(' ') } export const getIssueName = (postfix: string = generateId()): string => `issue-${postfix}` + +/** + * Return random capitalized string like "AFJKD" + * + * @returns string + */ +export function generateProjectId (size: number = 5): string { + return Array.from({ length: size }, () => String.fromCharCode(65 + Math.floor(Math.random() * 26))).join('') +} + export async function performPanelTest (page: Page, statuses: string[], panel: string, mode: string): Promise { const locator = page.locator('.list-container') const excluded = DEFAULT_STATUSES.filter((status) => !statuses.includes(status))