diff --git a/tests/sanity/tests/tracker.layout.spec.ts b/tests/sanity/tests/tracker.layout.spec.ts new file mode 100644 index 0000000000..d50050182c --- /dev/null +++ b/tests/sanity/tests/tracker.layout.spec.ts @@ -0,0 +1,169 @@ +import { test, expect, Page } from '@playwright/test' +import { + checkIssueFromList, + createIssue, + createProject, + createSprint, + DEFAULT_STATUSES, + DEFAULT_USER, + IssueProps, + navigate, + PRIORITIES, + setViewGroup, + setViewOrder, + ViewletSelectors +} from './tracker.utils' +import { generateId, PlatformSetting } from './utils' +test.use({ + storageState: PlatformSetting +}) + +const getIssueName = (postfix: string = generateId(5)): string => `issue-${postfix}` + +async function createIssues (page: Page, projects?: string[], sprints?: string[]): Promise { + const issuesProps = [] + for (let index = 0; index < 5; index++) { + const shiftedIndex = 4 - index + const issueProps = { + name: getIssueName(`layout-${shiftedIndex}`), + status: DEFAULT_STATUSES[shiftedIndex], + assignee: shiftedIndex % 2 === 0 ? DEFAULT_USER : 'Chen Rosamund', + priority: PRIORITIES[shiftedIndex], + project: projects !== undefined ? projects[index % projects.length] : undefined, + sprint: sprints !== undefined ? sprints[index % sprints.length] : undefined + } + issuesProps.push(issueProps) + + await createIssue(page, issueProps) + } + + return issuesProps +} + +async function createProjects (page: Page): Promise { + const projects = [] + + for (let index = 0; index < 5; index++) { + const prjId = `project-${generateId()}-${index}` + projects.push(prjId) + + await createProject(page, prjId) + } + + return projects +} + +async function createSprints (page: Page): Promise { + const sprints = [] + + for (let index = 0; index < 5; index++) { + const sprintId = `sprint-${generateId()}-${index}` + sprints.push(sprintId) + + await createSprint(page, sprintId) + } + + return sprints +} + +async function initIssues (page: Page): Promise { + const projects = await createProjects(page) + const sprints = await createSprints(page) + const issuesProps = await createIssues(page, projects, sprints) + await page.click('text="Issues"') + + return issuesProps +} + +test.describe('tracker layout tests', () => { + test.beforeEach(async ({ page }) => { + test.setTimeout(60000) + await navigate(page) + issuesProps = await initIssues(page) + }) + + let issuesProps: IssueProps[] = [] + const orders = ['Status', 'Last updated', 'Priority'] as const + const groups = ['Status', 'Assignee', 'Priority', 'Project', 'Sprint', 'No grouping'] as const + const groupsLabels: { [key in typeof groups[number]]?: string[] } = { + Status: DEFAULT_STATUSES, + Assignee: [DEFAULT_USER, 'Chen Rosamund'], + Priority: PRIORITIES, + 'No grouping': ['No grouping'] + } + + for (const group of groups) { + test(`issues-${group.toLowerCase()}-grouping-layout`, async ({ page }) => { + const locator = page.locator('.issueslist-container') + await setViewGroup(page, group) + + let groupLabels: any[] + if (group === 'Sprint') { + groupLabels = issuesProps.map((props) => props.sprint) + } else if (group === 'Project') { + groupLabels = issuesProps.map((props) => props.project) + } else { + groupLabels = groupsLabels[group] ?? [] + } + const issueNames = issuesProps.map((props) => props.name) + + await page.click(ViewletSelectors.Table) + await expect(locator).toContainText(groupLabels) + + for (const issueName of issueNames) { + await checkIssueFromList(page, issueName) + } + }) + } + + for (const order of orders) { + test(`issues-${order.toLowerCase()}-ordering-layout`, async ({ page }) => { + const locator = page.locator('.panel-container') + let orderedIssueNames: string[] + + if (order === 'Priority') { + orderedIssueNames = issuesProps + .sort((propsLeft, propsRight) => { + if (propsLeft.priority === undefined || propsRight.priority === undefined) { + return -1 + } + + if (propsLeft.priority === propsRight.priority) { + return 0 + } else if ( + PRIORITIES.findIndex((p) => p === propsLeft.priority) - + PRIORITIES.findIndex((p) => p === propsRight.priority) > + 0 + ) { + return 1 + } + return -1 + }) + .map((p) => p.name) + } else if (order === 'Status') { + orderedIssueNames = issuesProps + .sort((propsLeft, propsRight) => { + if (propsLeft.status !== undefined && propsRight.status !== undefined) { + if (propsLeft.status === propsRight.status) { + return 0 + } else if ( + DEFAULT_STATUSES.findIndex((s) => s === propsLeft.status) - + DEFAULT_STATUSES.findIndex((s) => s === propsRight.status) > + 0 + ) { + return 1 + } + } + + return -1 + }) + .map((p) => p.name) + } else { + orderedIssueNames = issuesProps.map((props) => props.name).reverse() + } + await setViewOrder(page, order) + await page.click(ViewletSelectors.Board) + await expect(locator).toContainText(orderedIssueNames) + }) + } +}) diff --git a/tests/sanity/tests/tracker.projects.spec.ts b/tests/sanity/tests/tracker.projects.spec.ts index 9471497341..34ecd87048 100644 --- a/tests/sanity/tests/tracker.projects.spec.ts +++ b/tests/sanity/tests/tracker.projects.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test' +import { navigate } from './tracker.utils' import { generateId, PlatformSetting, PlatformURI } from './utils' test.use({ @@ -13,6 +14,7 @@ test.describe('project tests', () => { test('create-project-issue', async ({ page }) => { await page.click('[id="app-tracker\\:string\\:TrackerApplication"]') + await navigate(page) // Click text=Projects await page.click('text=Projects') await expect(page).toHaveURL( diff --git a/tests/sanity/tests/tracker.spec.ts b/tests/sanity/tests/tracker.spec.ts index 50981d17b0..830f69ca9b 100644 --- a/tests/sanity/tests/tracker.spec.ts +++ b/tests/sanity/tests/tracker.spec.ts @@ -1,130 +1,32 @@ -import { test, expect, Page } from '@playwright/test' -import { generateId, PlatformSetting, PlatformURI } from './utils' +import { test, expect } from '@playwright/test' +import { + checkIssue, + createIssue, + createLabel, + createSubissue, + DEFAULT_STATUSES, + DEFAULT_USER, + navigate, + openIssue, + ViewletSelectors +} from './tracker.utils' +import { generateId, PlatformSetting } from './utils' test.use({ storageState: PlatformSetting }) -async function navigate (page: Page): Promise { - await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/sanity-ws`) - await page.click('[id="app-tracker\\:string\\:TrackerApplication"]') - await expect(page).toHaveURL(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/sanity-ws/tracker`) -} - -interface IssueProps { - name: string - description?: string - status?: string - labels?: string[] - priority?: string - assignee?: string -} - -async function fillIssueForm ( - page: Page, - { name, description, status, assignee, labels, priority }: IssueProps -): Promise { - await page.fill('[placeholder="Issue\\ title"]', name) - if (description !== undefined) { - await page.fill('.ProseMirror', description) - } - if (status !== undefined) { - await page.click('#status-editor') - await page.click(`.menu-item:has-text("${status}")`) - } - if (priority !== undefined) { - await page.click('button:has-text("No priority")') - await page.click(`.selectPopup button:has-text("${priority}")`) - } - if (labels !== undefined) { - await page.click('.button:has-text("Labels")') - for (const label of labels) { - await page.click(`.selectPopup button:has-text("${label}") >> nth=0`) - } - await page.keyboard.press('Escape') - } - if (assignee !== undefined) { - await page.click('.button:has-text("Assignee")') - await page.click(`.selectPopup button:has-text("${assignee}")`) - } -} - -async function createIssue (page: Page, props: IssueProps): Promise { - await page.waitForSelector('span:has-text("Default")') - await page.click('button:has-text("New issue")') - await fillIssueForm(page, props) - await page.click('button:has-text("Save issue")') - await page.waitForSelector('form.antiCard', { state: 'detached' }) -} - -async function createSubissue (page: Page, props: IssueProps): Promise { - await page.click('button:has-text("Add sub-issue")') - await fillIssueForm(page, props) - await page.click('button:has-text("Save")') -} - -interface LabelProps { - label: string -} -async function createLabel (page: Page, { label }: LabelProps): Promise { - await page.click('button:has-text("New issue")') - await page.click('button:has-text("Labels")') - await page.click('.buttons-group >> button >> nth=-1') - await page.fill('[id="tags:string:AddTag"] >> input >> nth=0', label) - await page.click('[id="tags:string:AddTag"] >> button:has-text("Create")') - await page.waitForSelector('form.antiCard[id="tags:string:AddTag"]', { state: 'detached' }) - await page.keyboard.press('Escape') - await page.waitForTimeout(100) - await page.keyboard.press('Escape') -} - -async function checkIssue ( - page: Page, - { name, description, status, assignee, labels, priority }: IssueProps -): Promise { - if (name !== undefined) { - await expect(page.locator('.popupPanel')).toContainText(name) - } - if (description !== undefined) { - await expect(page.locator('.popupPanel')).toContainText(description) - } - const asideLocator = page.locator('.popupPanel-body__aside') - if (status !== undefined) { - await expect(asideLocator).toContainText(status) - } - if (labels !== undefined) { - await expect(asideLocator).toContainText(labels) - } - if (priority !== undefined) { - await expect(asideLocator).toContainText(priority) - } - if (assignee !== undefined) { - await expect(asideLocator).toContainText(assignee) - } -} - -async function openIssue (page: Page, name: string): Promise { - await page.click(`.antiList__row:has-text("${name}") .issuePresenterRoot`) -} - -const defaultStatuses = ['Backlog', 'Todo', 'In Progress', 'Done', 'Canceled'] -const defaultUser = 'Appleseed John' -enum viewletSelectors { - Table = '.tablist-container >> div.button:nth-child(1)', - Board = '.tablist-container >> div.button:nth-child(2)' -} - test('create-issue-and-sub-issue', async ({ page }) => { const props = { name: getIssueName(), description: 'description', labels: ['label', 'another-label'], - status: defaultStatuses[0], + status: DEFAULT_STATUSES[0], priority: 'Urgent', - assignee: defaultUser + assignee: DEFAULT_USER } await navigate(page) for (const label of props.labels) { - await createLabel(page, { label }) + await createLabel(page, label) } await createIssue(page, props) await page.click('text="Issues"') @@ -140,22 +42,22 @@ const getIssueName = (postfix: string = generateId(5)): string => `issue-${postf test('issues-status-display', async ({ page }) => { const panelStatusMap = new Map([ - ['Issues', defaultStatuses], + ['Issues', DEFAULT_STATUSES], ['Active', ['Todo', 'In Progress']], ['Backlog', ['Backlog']] ]) const locator = page.locator('.issueslist-container') await navigate(page) - for (const status of defaultStatuses) { + for (const status of DEFAULT_STATUSES) { await createIssue(page, { name: getIssueName(status), status }) } for (const [panel, statuses] of panelStatusMap) { - const excluded = defaultStatuses.filter((status) => !statuses.includes(status)) + const excluded = DEFAULT_STATUSES.filter((status) => !statuses.includes(status)) await page.locator(`text="${panel}"`).click() - await page.click(viewletSelectors.Table) + await page.click(ViewletSelectors.Table) await expect(locator).toContainText(statuses) if (excluded.length > 0) await expect(locator).not.toContainText(excluded) - await page.click(viewletSelectors.Board) + await page.click(ViewletSelectors.Board) if (excluded.length > 0) await expect(locator).not.toContainText(excluded) for (const status of statuses) { await expect(page.locator(`.panel-container:has-text("${status}")`)).toContainText(getIssueName(status)) @@ -166,7 +68,7 @@ test('issues-status-display', async ({ page }) => { test('save-view-options', async ({ page }) => { const panels = ['Issues', 'Active', 'Backlog'] await navigate(page) - for (const viewletSelector of [viewletSelectors.Board, viewletSelectors.Table]) { + for (const viewletSelector of [ViewletSelectors.Board, ViewletSelectors.Table]) { for (const panel of panels) { await page.click(`text="${panel}"`) await page.click(viewletSelector) diff --git a/tests/sanity/tests/tracker.utils.ts b/tests/sanity/tests/tracker.utils.ts new file mode 100644 index 0000000000..8c13af90f6 --- /dev/null +++ b/tests/sanity/tests/tracker.utils.ts @@ -0,0 +1,168 @@ +import { Page, expect } from '@playwright/test' +import { PlatformURI } from './utils' + +export interface IssueProps { + name: string + description?: string + status?: string + labels?: string[] + priority?: string + assignee?: string + project?: string + sprint?: string +} + +export enum ViewletSelectors { + Table = '.tablist-container >> div.button:nth-child(1)', + Board = '.tablist-container >> div.button:nth-child(2)' +} + +export const PRIORITIES = ['No priority', 'Urgent', 'High', 'Medium', 'Low'] +export const DEFAULT_STATUSES = ['Backlog', 'Todo', 'In Progress', 'Done', 'Canceled'] +export const DEFAULT_USER = 'Appleseed John' + +export async function navigate (page: Page): Promise { + await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/sanity-ws`) + await page.click('[id="app-tracker\\:string\\:TrackerApplication"]') + await expect(page).toHaveURL(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/sanity-ws/tracker`) +} + +export async function setViewGroup (page: Page, groupName: string): Promise { + await page.click('button:has-text("View")') + await page.click('.antiCard >> button >> nth=0') + await page.click(`.menu-item:has-text("${groupName}")`) + await expect(page.locator('.antiCard >> button >> nth=0')).toContainText(groupName) + + await page.keyboard.press('Escape') +} + +export async function setViewOrder (page: Page, orderName: string): Promise { + await page.click('button:has-text("View")') + await page.click('.antiCard >> button >> nth=1') + await page.click(`.menu-item:has-text("${orderName}")`) + await expect(page.locator('.antiCard >> button >> nth=1')).toContainText(orderName) + + await page.keyboard.press('Escape') +} + +export async function fillIssueForm (page: Page, props: IssueProps): Promise { + const { name, description, status, assignee, labels, priority, project, sprint } = props + await page.fill('[placeholder="Issue\\ title"]', name) + if (description !== undefined) { + await page.fill('.ProseMirror', description) + } + if (status !== undefined) { + await page.click('#status-editor') + await page.click(`.menu-item:has-text("${status}")`) + } + if (priority !== undefined) { + await page.click('button:has-text("No priority")') + await page.click(`.selectPopup button:has-text("${priority}")`) + } + if (labels !== undefined) { + await page.click('.button:has-text("Labels")') + for (const label of labels) { + await page.click(`.selectPopup button:has-text("${label}") >> nth=0`) + } + await page.keyboard.press('Escape') + } + if (assignee !== undefined) { + await page.click('.button:has-text("Assignee")') + await page.click(`.selectPopup button:has-text("${assignee}")`) + } + if (project !== undefined) { + await page.click('form button:has-text("Project")') + await page.click(`.selectPopup button:has-text("${project}")`) + } + if (sprint !== undefined) { + await page.click('.button:has-text("No Sprint")') + await page.click(`.selectPopup button:has-text("${sprint}")`) + } +} + +export async function createIssue (page: Page, props: IssueProps): Promise { + await page.waitForSelector('span:has-text("Default")') + await page.click('button:has-text("New issue")') + await fillIssueForm(page, props) + await page.click('button:has-text("Save issue")') + await page.waitForSelector('form.antiCard', { state: 'detached' }) +} + +export async function createProject (page: Page, projectName: string): Promise { + await page.click('text=Projects') + await expect(page).toHaveURL( + `${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/sanity-ws/tracker/tracker%3Ateam%3ADefaultTeam/projects` + ) + await page.click('button:has-text("Project")') + await page.click('[placeholder="Project\\ name"]') + await page.fill('[placeholder="Project\\ name"]', projectName) + await page.click('button:has-text("Create project")') +} + +export async function createSprint (page: Page, sprintName: string): Promise { + await page.click('text=Sprints') + await expect(page).toHaveURL( + `${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/sanity-ws/tracker/tracker%3Ateam%3ADefaultTeam/sprints` + ) + await page.click('button:has-text("Sprint")') + await page.click('[placeholder="Sprint\\ name"]') + await page.fill('[placeholder="Sprint\\ name"]', sprintName) + await page.click('button:has-text("Create")') +} + +export async function createSubissue (page: Page, props: IssueProps): Promise { + await page.click('button:has-text("Add sub-issue")') + await fillIssueForm(page, props) + await page.click('button:has-text("Save")') +} + +export async function createLabel (page: Page, label: string): Promise { + await page.click('button:has-text("New issue")') + await page.click('button:has-text("Labels")') + await page.click('.buttons-group >> button >> nth=-1') + await page.fill('[id="tags:string:AddTag"] >> input >> nth=0', label) + await page.click('[id="tags:string:AddTag"] >> button:has-text("Create")') + await page.waitForSelector('form.antiCard[id="tags:string:AddTag"]', { state: 'detached' }) + await page.keyboard.press('Escape') + await page.waitForTimeout(100) + await page.keyboard.press('Escape') +} + +export async function checkIssue (page: Page, props: IssueProps): Promise { + const { name, description, status, assignee, labels, priority, project, sprint } = props + + if (name !== undefined) { + await expect(page.locator('.popupPanel')).toContainText(name) + } + if (description !== undefined) { + await expect(page.locator('.popupPanel')).toContainText(description) + } + const asideLocator = page.locator('.popupPanel-body__aside') + if (status !== undefined) { + await expect(asideLocator).toContainText(status) + } + if (labels !== undefined) { + await expect(asideLocator).toContainText(labels) + } + if (priority !== undefined) { + await expect(asideLocator).toContainText(priority) + } + if (assignee !== undefined) { + await expect(asideLocator).toContainText(assignee) + } + if (project !== undefined) { + await expect(asideLocator).toContainText(project) + } + if (sprint !== undefined) { + await expect(asideLocator).toContainText(sprint) + } +} + +export async function checkIssueFromList (page: Page, issueName: string): Promise { + await page.click(ViewletSelectors.Board) + await expect(page.locator(`.panel-container:has-text("${issueName}")`)).toContainText(issueName) +} + +export async function openIssue (page: Page, name: string): Promise { + await page.click(`.antiList__row:has-text("${name}") .issuePresenterRoot`) +}