mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-14 04:08:19 +00:00
TSK-473: Added tracker layout sanity tests (#2452)
Signed-off-by: Anton Brechka <anton.brechka@xored.com>
This commit is contained in:
parent
9c78dc8c9c
commit
4f842eaa8c
169
tests/sanity/tests/tracker.layout.spec.ts
Normal file
169
tests/sanity/tests/tracker.layout.spec.ts
Normal file
@ -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<IssueProps[]> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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<IssueProps[]> {
|
||||
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)
|
||||
})
|
||||
}
|
||||
})
|
@ -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(
|
||||
|
@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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)
|
||||
|
168
tests/sanity/tests/tracker.utils.ts
Normal file
168
tests/sanity/tests/tracker.utils.ts
Normal file
@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await page.click(`.antiList__row:has-text("${name}") .issuePresenterRoot`)
|
||||
}
|
Loading…
Reference in New Issue
Block a user