Merge remote-tracking branch 'origin/develop' into staging

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-09-19 14:02:52 +07:00
commit c6db33cc62
No known key found for this signature in database
GPG Key ID: BD80F68D68D8F7F2
13 changed files with 424 additions and 41 deletions

View File

@ -19,30 +19,36 @@
import {
Floor,
Invite,
isOffice,
JoinRequest,
loveId,
Office,
ParticipantInfo,
RequestStatus,
Room,
RoomType,
isOffice,
loveId
RoomType
} from '@hcengineering/love'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
import {
Location,
PopupResult,
closePopup,
eventToHTMLElement,
Location,
location,
PopupResult,
showPopup,
tooltip
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { onDestroy } from 'svelte'
import workbench from '@hcengineering/workbench'
import { closeWidget, openWidget, sidebarStore } from '@hcengineering/workbench-resources'
import {
closeWidget,
minimizeSidebar,
openWidget,
sidebarStore,
SidebarVariant
} from '@hcengineering/workbench-resources'
import love from '../plugin'
import {
@ -283,8 +289,20 @@
showPopup(CamSettingPopup, {}, eventToHTMLElement(e))
}
$: isVideoWidgetOpened = $sidebarStore.widgetsState.has(love.ids.VideoWidget)
$: if (
isVideoWidgetOpened &&
$sidebarStore.widget === undefined &&
$location.path[2] !== loveId &&
$sidebarStore.widgetsState.get(love.ids.VideoWidget)?.closedByUser !== true
) {
sidebarStore.update((s) => ({ ...s, widget: love.ids.VideoWidget, variant: SidebarVariant.EXPANDED }))
}
function checkActiveVideo (loc: Location, video: boolean, room: Ref<Room> | undefined): void {
const isOpened = $sidebarStore.widgetsState.get(love.ids.VideoWidget)
const isOpened = $sidebarStore.widgetsState.has(love.ids.VideoWidget)
if (room === undefined) {
if (isOpened) {
closeWidget(love.ids.VideoWidget)
@ -292,13 +310,22 @@
return
}
if (loc.path[2] !== loveId && video) {
if (isOpened) return
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: love.ids.VideoWidget })[0]
if (widget === undefined) return
openWidget(widget, {
room
})
if (video) {
if (!isOpened) {
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: love.ids.VideoWidget })[0]
if (widget === undefined) return
openWidget(
widget,
{
room
},
loc.path[2] !== loveId
)
}
if (loc.path[2] === loveId && $sidebarStore.widget === love.ids.VideoWidget) {
minimizeSidebar()
}
} else {
closeWidget(love.ids.VideoWidget)
}

View File

@ -79,7 +79,7 @@
</Scroller>
{/if}
{#if $floors.length > 1}
<div class="flex-row-center flex-reverse flex-no-shrink w-full mt-4">
<div class="flex-row-center flex-reverse flex-no-shrink w-full mt-4 mr-2">
<ModernButton on:click={changeMode} icon={IconLayers} label={love.string.ChangeFloor} />
</div>
{/if}
@ -92,7 +92,6 @@
flex-direction: column;
justify-content: space-between;
height: 100%;
padding-right: 0.5rem;
padding-bottom: 1rem;
}
</style>

View File

@ -379,7 +379,7 @@
<style lang="scss">
.error {
font-size: 500;
font-weight: 500;
font-size: 1.5rem;
align-items: center;
}

View File

@ -65,7 +65,7 @@
class="container flex-row-center flex-gap-2 active"
class:error={state.error}
on:click={handleClick}
use:tooltip={state.error !== undefined ? { label: getEmbeddedLabel(state.error) } : undefined}
use:tooltip={state.error != null ? { label: getEmbeddedLabel(state.error) } : undefined}
>
{#if state.error}
<IconError size={'small'} fill={'var(--negative-button-default)'} />

View File

@ -76,15 +76,19 @@ export function getUppy (options: FileUploadOptions, onFileUploaded?: FileUpload
method: 'POST',
headers: {
Authorization: 'Bearer ' + (getMetadata(presentation.metadata.Token) as string)
},
getResponseError: (_, response) => {
return new Error((response as Response).statusText)
}
// getResponseData: (body: string): UppyBody => {
// const data = JSON.parse(body)
// return {
// uuid: data[0].id
// }
// }
})
// Hack to setup shouldRetry callback on xhrUpload that is not exposed in options
const xhrUpload = uppy.getState().xhrUpload ?? {}
uppy.getState().xhrUpload = {
...xhrUpload,
shouldRetry: (response: Response) => response.status !== 413
}
uppy.addPreProcessor(async (fileIds: string[]) => {
for (const fileId of fileIds) {
const file = uppy.getFile(fileId)
@ -98,8 +102,12 @@ export function getUppy (options: FileUploadOptions, onFileUploaded?: FileUpload
if (onFileUploaded != null) {
uppy.addPostProcessor(async (fileIds: string[]) => {
for (const fileId of fileIds) {
const file = uppy.getFile(fileId)
// post-process only files without errors
const files = fileIds
.map((fileId) => uppy.getFile(fileId))
.filter((file) => !('error' in file && file.error != null))
for (const file of files) {
const uuid = file.meta.uuid as Ref<Blob>
if (uuid !== undefined) {
const metadata = await getFileMetadata(file.data, uuid)

View File

@ -18,8 +18,8 @@
import workbench from '../../plugin'
import { sidebarStore, SidebarVariant } from '../../sidebar'
import WidgetsBarMini from './SidebarMini.svelte'
import WidgetsBarExpanded from './SidebarExpanded.svelte'
import SidebarMini from './SidebarMini.svelte'
import SidebarExpanded from './SidebarExpanded.svelte'
const client = getClient()
@ -38,9 +38,9 @@
<div class="antiPanel-component antiComponent root size-{size}" id="sidebar">
{#if $sidebarStore.variant === SidebarVariant.MINI}
<WidgetsBarMini {widgets} {preferences} />
<SidebarMini {widgets} {preferences} />
{:else if $sidebarStore.variant === SidebarVariant.EXPANDED}
<WidgetsBarExpanded {widgets} {preferences} />
<SidebarExpanded {widgets} {preferences} />
{/if}
</div>
@ -62,9 +62,9 @@
}
&.size-medium {
width: 25rem !important;
min-width: 25rem !important;
max-width: 25rem !important;
width: 20rem !important;
min-width: 20rem !important;
max-width: 20rem !important;
}
}
</style>

View File

@ -19,7 +19,7 @@
import WidgetPresenter from './/WidgetPresenter.svelte'
import AddWidgetsPopup from './AddWidgetsPopup.svelte'
import { openWidget, sidebarStore, SidebarVariant } from '../../../sidebar'
import { minimizeSidebar, openWidget, sidebarStore } from '../../../sidebar'
export let widgets: Widget[] = []
export let preferences: WidgetPreference[] = []
@ -31,9 +31,9 @@
function handleSelectWidget (widget: Widget): void {
if (selected === widget._id) {
sidebarStore.update((state) => ({ ...state, widget: undefined, variant: SidebarVariant.MINI }))
minimizeSidebar(true)
} else {
openWidget(widget)
openWidget(widget, $sidebarStore.widgetsState.get(widget._id)?.data, true)
}
}

View File

@ -30,6 +30,7 @@ export interface WidgetState {
data?: Record<string, any>
tabs: WidgetTab[]
tab?: string
closedByUser?: boolean
}
export interface SidebarState {
@ -95,7 +96,7 @@ function setSidebarStateToLocalStorage (state: SidebarState): void {
)
}
export function openWidget (widget: Widget, data?: Record<string, any>): void {
export function openWidget (widget: Widget, data?: Record<string, any>, active = true): void {
const state = get(sidebarStore)
const { widgetsState } = state
const widgetState = widgetsState.get(widget._id)
@ -106,7 +107,7 @@ export function openWidget (widget: Widget, data?: Record<string, any>): void {
...state,
widgetsState,
variant: SidebarVariant.EXPANDED,
widget: widget._id
widget: active ? widget._id : state.widget
})
}
@ -114,6 +115,10 @@ export function closeWidget (widget: Ref<Widget>): void {
const state = get(sidebarStore)
const { widgetsState } = state
if (!widgetsState.has(widget) && state.widget !== widget && state.variant === SidebarVariant.MINI) {
return
}
widgetsState.delete(widget)
if (state.widget === widget) {
@ -292,3 +297,15 @@ export function isElementFromSidebar (element: HTMLElement): boolean {
return isDescendant(sidebarElement, element)
}
export function minimizeSidebar (closedByUser = false): void {
const state = get(sidebarStore)
const { widget, widgetsState } = state
const widgetState = widget == null ? undefined : widgetsState.get(widget)
if (widget !== undefined && widgetState !== undefined && closedByUser) {
widgetsState.set(widget, { ...widgetState, closedByUser })
}
sidebarStore.set({ ...state, ...widgetsState, widget: undefined, variant: SidebarVariant.MINI })
}

View File

@ -17,6 +17,7 @@ import { PlanningNavigationMenuPage } from '../model/planning/planning-navigatio
import { PlanningPage } from '../model/planning/planning-page'
import { SignUpData } from '../model/common-types'
import { TestData } from '../chat/types'
import { faker } from '@faker-js/faker'
const retryOptions = { intervals: [1000, 1500, 2500], timeout: 60000 }
@ -148,4 +149,150 @@ test.describe('Content in the Documents tests', () => {
await documentContentSecondPage.proseTableCell(1, 1).fill('Center')
await expect(documentContentPage.proseTableCell(1, 1)).toContainText('Center', { timeout: 5000 })
})
test.describe('Image in the document', () => {
test('Check Image alignment setting', async ({ page }) => {
await documentContentPage.addImageToDocument(page)
await test.step('Align image to right', async () => {
await documentContentPage.clickImageAlignButton('right')
await documentContentPage.checkImageAlign('right')
})
await test.step('Align image to left', async () => {
await documentContentPage.clickImageAlignButton('left')
await documentContentPage.checkImageAlign('left')
})
await test.step('Align image to center', async () => {
await documentContentPage.clickImageAlignButton('center')
await documentContentPage.checkImageAlign('center')
})
})
test('Check Image view and size actions', async ({ page }) => {
await documentContentPage.addImageToDocument(page)
const imageSrc = await documentContentPage.firstImageInDocument().getAttribute('src')
await test.step('Set size of image to the 25%', async () => {
await documentContentPage.clickImageSizeButton('25%')
await documentContentPage.checkImageSize('25%')
})
await test.step('Set size of image to the 50%', async () => {
await documentContentPage.clickImageSizeButton('50%')
await documentContentPage.checkImageSize('50%')
})
await test.step('Set size of image to the 100%', async () => {
await documentContentPage.clickImageSizeButton('100%')
await documentContentPage.checkImageSize('100%')
})
await test.step('Set size of image to the unset', async () => {
const IMAGE_ORIGINAL_SIZE = 199
await documentContentPage.clickImageSizeButton('Unset')
await documentContentPage.checkImageSize(IMAGE_ORIGINAL_SIZE)
})
await test.step('User can open image in fullscreen on current page', async () => {
await documentContentPage.clickImageFullscreenButton()
await expect(documentContentPage.fullscreenImage()).toBeVisible()
await documentContentPage.page.keyboard.press('Escape')
await expect(documentContentPage.fullscreenImage()).toBeHidden()
})
await test.step('User can open image original in the new tab', async () => {
const [newPage] = await Promise.all([
page.waitForEvent('popup'),
documentContentPage.clickImageOriginalButton()
])
await newPage.waitForLoadState('domcontentloaded')
expect(newPage.url()).toBe(imageSrc)
await newPage.close()
})
})
test('Remove image with Backspace', async ({ page }) => {
await documentContentPage.addImageToDocument(page)
await documentContentPage.selectedFirstImageInDocument()
await documentContentPage.page.keyboard.press('Backspace')
await expect(documentContentPage.firstImageInDocument()).toBeHidden()
})
test('Check Table of Content', async ({ page }) => {
const HEADER_1_CONTENT = 'Header 1'
const HEADER_2_CONTENT = 'Header 2'
const HEADER_3_CONTENT = 'Header 3'
const contentParts = [
`# ${HEADER_1_CONTENT}\n\n${faker.lorem.paragraph(20)}\n`,
`## ${HEADER_2_CONTENT}\n\n${faker.lorem.paragraph(20)}\n`,
`### ${HEADER_3_CONTENT}\n\n${faker.lorem.paragraph(20)}`
]
await test.step('Fill in the document and check the appearance of the ToC items', async () => {
await documentContentPage.inputContentParapraph().click()
let partIndex = 0
for (const contentPart of contentParts) {
await documentContentPage.page.keyboard.type(contentPart)
await expect(documentContentPage.tocItems()).toHaveCount(++partIndex)
}
})
await test.step('Check if ToC element is visible', async () => {
await expect(documentContentPage.page.locator('.toc-container .toc-item')).toHaveCount(3)
})
await test.step('User go to first header by ToC', async () => {
await documentContentPage.tocItems().first().click()
await documentContentPage.buttonTocPopupHeader(HEADER_1_CONTENT).click()
await expect(documentContentPage.headerElementInDocument('h1', HEADER_1_CONTENT)).toBeInViewport()
})
await test.step('User go to last header by ToC', async () => {
await documentContentPage.tocItems().first().click()
await documentContentPage.buttonTocPopupHeader(HEADER_3_CONTENT).click()
await expect(documentContentPage.headerElementInDocument('h3', HEADER_3_CONTENT)).toBeInViewport()
})
})
})
test('Check a slash typing handling', async ({ page }) => {
await test.step('User can open the popup if types "/" in empty document', async () => {
await documentContentPage.inputContentParapraph().click()
await documentContentPage.page.keyboard.type('/')
await expect(documentContentPage.slashActionItemsPopup()).toBeVisible()
await documentContentPage.page.keyboard.press('Escape')
})
await test.step('User can open the popup if types "/" after some content', async () => {
await documentContentPage.inputContentParapraph().click()
await documentContentPage.page.keyboard.type('First paragraph\n\n')
await documentContentPage.page.keyboard.type('/')
await expect(documentContentPage.slashActionItemsPopup()).toBeVisible()
await documentContentPage.page.keyboard.press('Escape')
})
await test.step('User cannot open a popup if he types "/" inside code block', async () => {
await documentContentPage.page.keyboard.press('Enter')
await documentContentPage.page.keyboard.type('/')
await documentContentPage.menuPopupItemButton('Code block').click()
await documentContentPage.page.keyboard.type('/')
await expect(documentContentPage.slashActionItemsPopup()).toBeHidden()
await documentContentPage.page.keyboard.press('ArrowDown')
await documentContentPage.page.keyboard.press('ArrowDown')
})
await test.step('User can create table by slash and open a popup if he types "/" inside a table', async () => {
await documentContentPage.page.keyboard.type('/')
await documentContentPage.menuPopupItemButton('Table').click()
await documentContentPage.menuPopupItemButton('1x2').first().click()
await documentContentPage.proseTableCell(0, 1).click()
await documentContentPage.page.keyboard.type('/')
await expect(documentContentPage.slashActionItemsPopup()).toBeVisible()
await documentContentPage.page.keyboard.press('Escape')
})
})
})

View File

@ -87,6 +87,43 @@ test.describe('Documents tests', () => {
await documentContentPage.checkDocumentTitle(moveDocument.title)
})
test('Create a document inside another document', async () => {
const contentFirst = 'Text first line'
const parentTeamspace: NewTeamspace = {
title: `Parent Teamspace-${generateId()}`,
description: 'Parent Teamspace description',
private: false
}
const parentDocument: NewDocument = {
title: `Parent Document Title-${generateId()}`,
space: parentTeamspace.title
}
const childDocument: NewDocument = {
title: `Child Document Title-${generateId()}`,
space: parentTeamspace.title
}
await test.step('Create a parent document by button "+" in left menu documents list', async () => {
await leftSideMenuPage.clickDocuments()
await documentsPage.checkTeamspaceNotExist(parentTeamspace.title)
await documentsPage.createNewTeamspace(parentTeamspace)
await documentsPage.checkTeamspaceExist(parentTeamspace.title)
await documentsPage.clickOnButtonCreateDocument()
await documentsPage.createDocument(parentDocument)
})
await test.step('Create a child document', async () => {
await documentsPage.clickAddDocumentIntoDocument(parentDocument.title)
await documentContentPage.updateDocumentTitle(childDocument.title)
const content = await documentContentPage.addContentToTheNewLine(contentFirst)
await documentContentPage.checkContent(content)
})
await test.step('Check nesting of documents', async () => {
await documentsPage.checkIfParentDocumentIsExistInBreadcrumbs(parentDocument.title)
})
})
test('Collaborative edit document content', async ({ page, browser }) => {
let content = ''
const contentFirstUser = 'First first!!! This string comes from the first user'

View File

@ -1,5 +1,6 @@
import { type Locator, type Page, expect } from '@playwright/test'
import { CommonPage } from '../common-page'
import { uploadFile } from '../../utils'
export class DocumentContentPage extends CommonPage {
readonly page: Page
@ -11,11 +12,17 @@ export class DocumentContentPage extends CommonPage {
readonly buttonDocumentTitle = (): Locator => this.page.locator('div[class*="main-content"] div.title input')
readonly inputContent = (): Locator => this.page.locator('div.textInput div.tiptap')
readonly selectContent = (): Locator => this.page.locator('div.textInput .select-text')
readonly inputContentParapraph = (): Locator => this.page.locator('div.textInput div.tiptap > p')
readonly leftMenu = (): Locator => this.page.locator('div.tiptap-left-menu')
readonly proseTableCell = (row: number, col: number): Locator =>
this.page.locator('table.proseTable').locator('tr').nth(row).locator('td').nth(col).locator('p')
readonly firstImageInDocument = (): Locator => this.page.locator('.textInput .text-editor-image-container img')
readonly tooltipImageTools = (): Locator => this.page.locator('.tippy-box')
readonly fullscreenImage = (): Locator => this.page.locator('.popup.fullsize img')
readonly proseTableColumnHandle = (col: number): Locator =>
this.page.locator('table.proseTable').locator('tr').first().locator('td').nth(col).locator('div.table-col-handle')
@ -45,6 +52,15 @@ export class DocumentContentPage extends CommonPage {
readonly assigneeToDo = (hasText: string): Locator => this.rowToDo(hasText).locator('div.assignee')
readonly checkboxToDo = (hasText: string): Locator => this.rowToDo(hasText).locator('input.chBox')
readonly tocItems = (): Locator => this.page.locator('.toc-container .toc-item')
readonly buttonTocPopupHeader = (headerText: string): Locator =>
this.page.locator(`.popup button:has-text("${headerText}")`)
readonly headerElementInDocument = (headerType: 'h1' | 'h2' | 'h3' = 'h1', text: string): Locator =>
this.page.locator(`.textInput ${headerType}:has-text("${text}")`)
readonly slashActionItemsPopup = (): Locator => this.page.locator('.selectPopup')
async checkDocumentTitle (title: string): Promise<void> {
await expect(this.buttonDocumentTitle()).toHaveValue(title)
}
@ -65,6 +81,113 @@ export class DocumentContentPage extends CommonPage {
await expect(this.inputContent()).toHaveText(content)
}
async checkUserAddedImage (): Promise<void> {
await expect(this.firstImageInDocument()).toBeVisible()
}
async checkIfImageToolsIsVisible (): Promise<void> {
await expect(this.tooltipImageTools()).toBeVisible()
}
async clickImageToolsButton (dataId: string): Promise<void> {
await this.tooltipImageTools().locator(`[data-id$="${dataId}"]`).click()
}
async selectedFirstImageInDocument (): Promise<void> {
await this.firstImageInDocument().click()
}
async checkIfImageHasAttribute (attribute: string, value: string): Promise<void> {
await expect(this.firstImageInDocument()).toHaveAttribute(attribute, value)
}
async clickImageAlignButton (align: 'left' | 'center' | 'right'): Promise<void> {
await this.selectedFirstImageInDocument()
await this.checkIfImageToolsIsVisible()
switch (align) {
case 'left':
await this.clickImageToolsButton('btnAlignLeft')
break
case 'right':
await this.clickImageToolsButton('btnAlignRight')
break
case 'center':
await this.clickImageToolsButton('btnAlignCenter')
break
}
}
async clickImageSizeButton (size: string | number): Promise<void> {
await this.selectedFirstImageInDocument()
await this.checkIfImageToolsIsVisible()
await this.clickImageToolsButton('btnMoreActions')
await this.page.locator(`.popup button:has-text("${size}")`).click()
}
async clickImageFullscreenButton (): Promise<void> {
await this.selectedFirstImageInDocument()
await this.checkIfImageToolsIsVisible()
await this.clickImageToolsButton('btnViewImage')
}
async clickImageOriginalButton (): Promise<void> {
await this.selectedFirstImageInDocument()
await this.checkIfImageToolsIsVisible()
await this.clickImageToolsButton('btnViewOriginal')
}
async checkImageAlign (side: 'left' | 'right' | 'center' = 'left'): Promise<void> {
const imageBox = await this.firstImageInDocument().boundingBox()
const parentBox = await this.selectContent().boundingBox()
if (!(imageBox !== null && parentBox !== null)) {
throw new Error('Image or parent box is not found')
}
const elementLeftEdge = imageBox.x
const parentLeftEdge = parentBox.x
const elementRightEdge = imageBox.x + imageBox.width
const parentRightEdge = parentBox.x + parentBox.width
switch (side) {
case 'right':
expect(elementRightEdge).toEqual(parentRightEdge)
break
case 'left':
expect(elementLeftEdge).toEqual(parentLeftEdge)
break
case 'center':
expect(elementLeftEdge - parentLeftEdge).toBeGreaterThan(0)
expect(elementLeftEdge - parentLeftEdge).toEqual(parentRightEdge - elementRightEdge)
break
}
}
async checkImageSize (size: '25%' | '50%' | '100%' | number): Promise<void> {
const imageBox = await this.firstImageInDocument().boundingBox()
const parentBox = await this.selectContent().boundingBox()
if (!(imageBox !== null && parentBox !== null)) {
throw new Error('Image or parent box is not found')
}
switch (size) {
case '25%':
expect(imageBox.width).toEqual(parentBox.width / 4)
break
case '50%':
expect(imageBox.width).toEqual(parentBox.width / 2)
break
case '100%':
expect(imageBox.width).toEqual(parentBox.width)
break
default:
expect(imageBox.width).toEqual(size)
break
}
}
async updateDocumentTitle (title: string): Promise<void> {
await this.buttonDocumentTitle().fill(title)
await this.buttonDocumentTitle().blur()
@ -110,4 +233,11 @@ export class DocumentContentPage extends CommonPage {
await this.rowToDo(text).hover()
await expect(this.checkboxToDo(text)).toBeChecked({ checked, timeout: 5000 })
}
async addImageToDocument (page: Page): Promise<void> {
await this.inputContentParapraph().click()
await this.leftMenu().click()
await uploadFile(page, 'cat3.jpeg', 'Image')
await this.checkUserAddedImage()
}
}

View File

@ -20,8 +20,16 @@ export class DocumentsPage extends CommonPage {
readonly buttonCreateDocument = (): Locator =>
this.page.locator('div[data-float="navigator"] button[id="new-document"]')
readonly buttonDocument = (name: string): Locator =>
this.page.locator('button.hulyNavItem-container > span[class*="label"]', { hasText: name })
readonly buttonDocumentWrapper = (name: string): Locator =>
this.page.locator(`button.hulyNavItem-container:has-text("${name}")`)
readonly buttonDocument = (name: string): Locator => this.buttonDocumentWrapper(name).locator('span[class*="label"]')
readonly buttonAddDocumentToDocument = (name: string): Locator =>
this.buttonDocumentWrapper(name).getByTestId('document:string:CreateDocument')
readonly breadcrumbsByDocumentParent = (parentDocumentTitle: string): Locator =>
this.page.locator(`.hulyHeader-titleGroup:has-text("${parentDocumentTitle}")`)
readonly buttonDocumentsApp = (): Locator => this.page.locator('button[id$="document:string:DocumentApplication"]')
readonly divTeamspacesParent = (): Locator =>
@ -123,6 +131,11 @@ export class DocumentsPage extends CommonPage {
await this.selectFromDropdown(this.page, popupItem)
}
async clickAddDocumentIntoDocument (documentTitle: string): Promise<void> {
await this.buttonDocumentWrapper(documentTitle).hover()
await this.buttonAddDocumentToDocument(documentTitle).click()
}
async openDocumentForTeamspace (spaceName: string, documentName: string): Promise<void> {
await this.page
.locator('button.hulyNavGroup-header span[class*="label"]', { hasText: spaceName })
@ -176,4 +189,8 @@ export class DocumentsPage extends CommonPage {
await expect(this.rowTeamspace(name)).toBeVisible()
await this.buttonJoinTeamspace(name).click()
}
async checkIfParentDocumentIsExistInBreadcrumbs (parentDocumentTitle: string): Promise<void> {
await expect(this.breadcrumbsByDocumentParent(parentDocumentTitle)).toBeVisible()
}
}

View File

@ -12,6 +12,7 @@ const config: PlaywrightTestConfig = {
{
name: 'Platform',
use: {
testIdAttribute: 'data-id',
permissions: ['clipboard-read', 'clipboard-write'],
...devices['Desktop Chrome'],
screenshot: 'only-on-failure',