From 0d1d1a8b8d16b115b71b96930dfb865f29c087d7 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Tue, 16 Apr 2024 14:53:53 +0700 Subject: [PATCH] UBERF-6330: Fix race conditions in UI (#5184) Signed-off-by: Andrey Sobolev --- packages/core/src/measurements/context.ts | 13 +- packages/model/src/utils.ts | 11 +- .../src/components/DocPopup.svelte | 4 +- .../src/components/ObjectSearchPopup.svelte | 8 +- .../src/components/SpaceSelect.svelte | 17 +- packages/presentation/src/utils.ts | 41 +++- .../src/components/MentionPopup.svelte | 8 +- packages/ui/src/components/Popup.svelte | 1 + .../ui/src/components/PopupInstance.svelte | 20 +- packages/ui/src/popups.ts | 16 +- .../src/components/ActivityFilter.svelte | 2 +- .../src/components/CalendarView.svelte | 4 +- .../src/components/Events.svelte | 2 +- .../src/components/Channel.svelte | 2 +- .../src/components/ChannelHeader.svelte | 6 +- .../src/components/ChannelMembers.svelte | 2 +- .../src/components/Replies.svelte | 10 +- .../src/components/chat/ChannelAside.svelte | 10 +- .../src/components/AssigneeBox.svelte | 8 +- .../src/components/Avatar.svelte | 36 ++-- .../src/components/UserBox.svelte | 4 +- .../src/components/NewDocumentHeader.svelte | 2 +- .../node-view/ToDoItemNodeView.svelte | 4 +- .../src/components/DocTagsEditor.svelte | 17 +- .../src/components/DraftTagsEditor.svelte | 19 +- .../src/components/DraftTagsPopup.svelte | 15 +- .../src/components/LabelsPresenter.svelte | 6 +- .../src/components/TagsAttributeEditor.svelte | 6 +- .../src/components/TagsDropdownEditor.svelte | 5 + .../src/components/TagsEditor.svelte | 5 + .../src/components/TagsPopup.svelte | 19 +- plugins/tags-resources/src/utils.ts | 21 +- .../src/components/AssignedTasks.svelte | 15 +- .../src/components/StatusTableView.svelte | 11 +- .../state/StateIconPresenter.svelte | 18 +- .../components/state/StatePresenter.svelte | 6 +- .../src/components/Templates.svelte | 54 ++--- .../src/components/CreateToDo.svelte | 4 +- .../src/components/CreateToDoPopup.svelte | 6 +- .../src/components/PlanningCalendar.svelte | 2 +- .../src/components/CreateIssue.svelte | 3 +- .../components/ComponentSelector.svelte | 22 +- .../issues/IssueStatusActivity.svelte | 4 +- .../milestones/MilestoneSelector.svelte | 12 +- .../src/components/ActionHandler.svelte | 11 +- .../src/components/ActionsPopup.svelte | 67 ++++-- .../src/components/EditDoc.svelte | 15 +- .../src/components/Table.svelte | 27 +-- .../src/components/filter/FilterBar.svelte | 6 +- .../src/components/filter/FilterButton.svelte | 8 +- .../src/components/list/List.svelte | 9 +- .../src/components/list/ListCategories.svelte | 91 ++++---- .../src/components/SavedView.svelte | 21 +- .../src/components/SpaceView.svelte | 12 +- .../src/components/Workbench.svelte | 50 ++--- .../src/components/icons/TodoCheck.svelte | 11 + .../src/components/icons/TodoUncheck.svelte | 10 + server/account/src/index.ts | 8 +- server/core/src/fulltext.ts | 11 +- server/tool/src/index.ts | 15 +- .../tests/collaborative/applications.spec.ts | 113 +++++----- .../sanity/tests/collaborative/issues.spec.ts | 204 ++++++++++-------- .../tests/documents/documents-link.spec.ts | 82 +++---- .../sanity/tests/documents/documents.spec.ts | 29 +-- tests/sanity/tests/model/common-page.ts | 1 + .../model/documents/document-content-page.ts | 2 + .../tests/model/planning/planning-page.ts | 5 +- .../model/tracker/issues-details-page.ts | 3 +- .../sanity/tests/model/tracker/issues-page.ts | 15 +- tests/sanity/tests/tracker/common-steps.ts | 2 +- tests/sanity/tests/tracker/component.spec.ts | 3 - tests/sanity/tests/tracker/filter.spec.ts | 1 + tests/sanity/tests/tracker/issues.spec.ts | 2 + .../sanity/tests/tracker/public-link.spec.ts | 114 ++++++---- tests/sanity/tests/tracker/subissues.spec.ts | 2 +- tests/sanity/tests/utils.ts | 6 +- 76 files changed, 862 insertions(+), 595 deletions(-) create mode 100644 plugins/workbench-resources/src/components/icons/TodoCheck.svelte create mode 100644 plugins/workbench-resources/src/components/icons/TodoUncheck.svelte diff --git a/packages/core/src/measurements/context.ts b/packages/core/src/measurements/context.ts index 8c591e46ba..edbd82384b 100644 --- a/packages/core/src/measurements/context.ts +++ b/packages/core/src/measurements/context.ts @@ -27,12 +27,21 @@ export class MeasureMetricsContext implements MeasureContext { this.logger.logOperation(this.name, spend, { ...params, ...fullParams }) }) + const errorPrinter = ({ message, stack, ...rest }: Error): object => ({ + message, + stack, + ...rest + }) + function replacer (value: any): any { + return value instanceof Error ? errorPrinter(value) : value + } + this.logger = logger ?? { info: (msg, args) => { - console.info(msg, ...Object.entries(args ?? {}).map((it) => `${it[0]}=${JSON.stringify(it[1])}`)) + console.info(msg, ...Object.entries(args ?? {}).map((it) => `${it[0]}=${JSON.stringify(replacer(it[1]))}`)) }, error: (msg, args) => { - console.error(msg, ...Object.entries(args ?? {}).map((it) => `${it[0]}=${JSON.stringify(it[1])}`)) + console.error(msg, ...Object.entries(args ?? {}).map((it) => `${it[0]}=${JSON.stringify(replacer(it[1]))}`)) }, close: async () => {}, logOperation: (operation, time, params) => {} diff --git a/packages/model/src/utils.ts b/packages/model/src/utils.ts index 3d80bff78a..e7a4f8e711 100644 --- a/packages/model/src/utils.ts +++ b/packages/model/src/utils.ts @@ -60,6 +60,15 @@ export interface ModelLogger { error: (msg: string, err: any) => void } +const errorPrinter = ({ message, stack, ...rest }: Error): object => ({ + message, + stack, + ...rest +}) +function replacer (value: any): any { + return value instanceof Error ? errorPrinter(value) : value +} + /** * @public */ @@ -68,6 +77,6 @@ export const consoleModelLogger: ModelLogger = { console.log(msg, data) }, error (msg: string, data: any): void { - console.error(msg, data) + console.error(msg, replacer(data)) } } diff --git a/packages/presentation/src/components/DocPopup.svelte b/packages/presentation/src/components/DocPopup.svelte index 5d22f84781..5e0275fbdc 100644 --- a/packages/presentation/src/components/DocPopup.svelte +++ b/packages/presentation/src/components/DocPopup.svelte @@ -116,7 +116,7 @@ if (key.code === 'Enter') { key.preventDefault() key.stopPropagation() - handleSelection(key, objects, selection) + void handleSelection(key, objects, selection) } } const manager = createFocusManager() @@ -217,7 +217,7 @@ class="menu-item withList w-full flex-row-center" disabled={readonly || isDeselectDisabled || loading} on:click={() => { - handleSelection(undefined, objects, item) + void handleSelection(undefined, objects, item) }} > diff --git a/packages/presentation/src/components/ObjectSearchPopup.svelte b/packages/presentation/src/components/ObjectSearchPopup.svelte index 07d2561e21..01655d2c17 100644 --- a/packages/presentation/src/components/ObjectSearchPopup.svelte +++ b/packages/presentation/src/components/ObjectSearchPopup.svelte @@ -33,7 +33,7 @@ import presentation from '../plugin' import { ObjectSearchCategory, ObjectSearchResult } from '../types' import { getClient } from '../utils' - import { hasResource } from '..' + import { hasResource, reduceCalls } from '..' export let query: string = '' export let label: IntlString | undefined = undefined @@ -108,7 +108,7 @@ export function done () {} - async function updateItems ( + const updateItems = reduceCalls(async function updateItems ( cat: ObjectSearchCategory | undefined, query: string, relatedDocuments?: RelatedDocument[] @@ -148,8 +148,8 @@ } } } - } - $: updateItems(category, query, relatedDocuments) + }) + $: void updateItems(category, query, relatedDocuments) const manager = createFocusManager() diff --git a/packages/presentation/src/components/SpaceSelect.svelte b/packages/presentation/src/components/SpaceSelect.svelte index b5c0d7592d..c76b9b5640 100644 --- a/packages/presentation/src/components/SpaceSelect.svelte +++ b/packages/presentation/src/components/SpaceSelect.svelte @@ -39,7 +39,7 @@ import view, { IconProps } from '@hcengineering/view' import { ObjectCreate } from '../types' - import { getClient } from '../utils' + import { getClient, reduceCalls } from '../utils' import SpacesPopup from './SpacesPopup.svelte' export let _class: Ref> @@ -73,19 +73,24 @@ const dispatch = createEventDispatcher() const mgr = getFocusManager() - async function updateSelected (_value: Ref | undefined, spaceQuery: DocumentQuery | undefined) { - selected = _value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: _value }) : undefined + const updateSelected = reduceCalls(async function ( + _value: Ref | undefined, + spaceQuery: DocumentQuery | undefined + ) { + let v = _value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: _value }) : undefined + selected = v if (selected === undefined && autoSelect) { - selected = (await findDefaultSpace?.()) ?? (await client.findOne(_class, { ...(spaceQuery ?? {}) })) + v = (await findDefaultSpace?.()) ?? (await client.findOne(_class, { ...(spaceQuery ?? {}) })) + selected = v if (selected !== undefined) { value = selected._id ?? undefined } } dispatch('object', selected) - } + }) - $: updateSelected(value, spaceQuery) + $: void updateSelected(value, spaceQuery) const showSpacesPopup = (ev: MouseEvent) => { if (readonly) { diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index 9f90c940ea..362e484805 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -14,6 +14,7 @@ // limitations under the License. // +import { Analytics } from '@hcengineering/analytics' import core, { TxOperations, getCurrentAccount, @@ -51,7 +52,6 @@ import { onDestroy } from 'svelte' import { type KeyedAttribute } from '..' import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline' import plugin from './plugin' -import { Analytics } from '@hcengineering/analytics' let liveQuery: LQ let client: TxOperations & MeasureClient @@ -552,3 +552,42 @@ export function decodeTokenPayload (token: string): any { export function isAdminUser (): boolean { return decodeTokenPayload(getMetadata(plugin.metadata.Token) ?? '').admin === 'true' } + +type ReduceParameters any> = T extends (...args: infer P) => any ? P : never + +interface NextCall { + op: () => Promise +} + +/** + * Utility method to skip middle update calls, optimistically if update function is called multiple times with few different parameters, only the last variant will be executed. + * The last invocation is executed after a few cycles, allowing to skip middle ones. + * + * This method can be used inside Svelte components to collapse complex update logic and handle interactions. + */ +export function reduceCalls) => Promise> ( + operation: T +): (...args: ReduceParameters) => Promise { + let nextCall: NextCall | undefined + let currentCall: NextCall | undefined + + const next = (): void => { + currentCall = nextCall + nextCall = undefined + if (currentCall !== undefined) { + void currentCall.op() + } + } + return async function (...args: ReduceParameters): Promise { + const myOp = async (): Promise => { + await operation(...args) + next() + } + + nextCall = { op: myOp } + await Promise.resolve() + if (currentCall === undefined) { + next() + } + } +} diff --git a/packages/text-editor/src/components/MentionPopup.svelte b/packages/text-editor/src/components/MentionPopup.svelte index 37670cc2e9..6fc2204af0 100644 --- a/packages/text-editor/src/components/MentionPopup.svelte +++ b/packages/text-editor/src/components/MentionPopup.svelte @@ -14,10 +14,10 @@ // limitations under the License. --> diff --git a/packages/ui/src/components/Popup.svelte b/packages/ui/src/components/Popup.svelte index 68d15a1790..ebe609a912 100644 --- a/packages/ui/src/components/Popup.svelte +++ b/packages/ui/src/components/Popup.svelte @@ -43,5 +43,6 @@ close={popup.close} {contentPanel} overlay={popup.options.overlay} + {popup} /> {/each} diff --git a/packages/ui/src/components/PopupInstance.svelte b/packages/ui/src/components/PopupInstance.svelte index 5ee31c0088..d1d581a294 100644 --- a/packages/ui/src/components/PopupInstance.svelte +++ b/packages/ui/src/components/PopupInstance.svelte @@ -16,11 +16,11 @@ diff --git a/plugins/chunter-resources/src/components/Replies.svelte b/plugins/chunter-resources/src/components/Replies.svelte index 2b476d5659..1dd7b9a216 100644 --- a/plugins/chunter-resources/src/components/Replies.svelte +++ b/plugins/chunter-resources/src/components/Replies.svelte @@ -13,11 +13,10 @@ // limitations under the License. --> diff --git a/plugins/tags-resources/src/components/DraftTagsPopup.svelte b/plugins/tags-resources/src/components/DraftTagsPopup.svelte index 0c82e4c995..48b56c97ec 100644 --- a/plugins/tags-resources/src/components/DraftTagsPopup.svelte +++ b/plugins/tags-resources/src/components/DraftTagsPopup.svelte @@ -25,18 +25,19 @@ const dispatch = createEventDispatcher() async function addRef ({ title, color, _id: tag }: TagElement): Promise { - tags.push({ - tag, - title, - color - }) - tags = tags + tags = [ + ...tags, + { + tag, + title, + color + } + ] dispatch('update', tags) } async function removeTag (tag: TagElement): Promise { tags = tags.filter((t) => t.tag !== tag._id) - tags = tags dispatch('update', tags) } diff --git a/plugins/tags-resources/src/components/LabelsPresenter.svelte b/plugins/tags-resources/src/components/LabelsPresenter.svelte index 6935d3a6c7..3d98bb1ac7 100644 --- a/plugins/tags-resources/src/components/LabelsPresenter.svelte +++ b/plugins/tags-resources/src/components/LabelsPresenter.svelte @@ -40,7 +40,11 @@ } async function tagsHandler (evt: MouseEvent): Promise { - showPopup(TagsEditorPopup, { object }, getEventPopupPositionElement(evt)) + showPopup(TagsEditorPopup, { object }, getEventPopupPositionElement(evt), undefined, undefined, { + refId: 'TagsPopup', + category: 'popup', + overlay: true + }) } let allWidth: number diff --git a/plugins/tags-resources/src/components/TagsAttributeEditor.svelte b/plugins/tags-resources/src/components/TagsAttributeEditor.svelte index 638538d71a..00691fe615 100644 --- a/plugins/tags-resources/src/components/TagsAttributeEditor.svelte +++ b/plugins/tags-resources/src/components/TagsAttributeEditor.svelte @@ -24,7 +24,11 @@ }) async function tagsHandler (evt: MouseEvent): Promise { if (readonly) return - showPopup(TagsEditorPopup, { object, targetClass }, getEventPopupPositionElement(evt)) + showPopup(TagsEditorPopup, { object, targetClass }, getEventPopupPositionElement(evt), undefined, undefined, { + refId: 'TagsPopup', + category: 'popup', + overlay: true + }) } async function removeTag (tag: TagReference): Promise { if (tag !== undefined) await client.remove(tag) diff --git a/plugins/tags-resources/src/components/TagsDropdownEditor.svelte b/plugins/tags-resources/src/components/TagsDropdownEditor.svelte index ad1219593f..d2eceeb5d9 100644 --- a/plugins/tags-resources/src/components/TagsDropdownEditor.svelte +++ b/plugins/tags-resources/src/components/TagsDropdownEditor.svelte @@ -73,6 +73,11 @@ } } } + }, + { + refId: 'TagsPopup', + category: 'popup', + overlay: true } ) } diff --git a/plugins/tags-resources/src/components/TagsEditor.svelte b/plugins/tags-resources/src/components/TagsEditor.svelte index a9c3d4c845..0c1161211e 100644 --- a/plugins/tags-resources/src/components/TagsEditor.svelte +++ b/plugins/tags-resources/src/components/TagsEditor.svelte @@ -79,6 +79,11 @@ if (result.action === 'add') addRef(result.tag) else if (result.action === 'remove') removeTag(items.filter((it) => it.tag === result.tag._id)[0]._id) } + }, + { + refId: 'TagsPopup', + category: 'popup', + overlay: true } ) } diff --git a/plugins/tags-resources/src/components/TagsPopup.svelte b/plugins/tags-resources/src/components/TagsPopup.svelte index 84accf5e50..0dbcef9d13 100644 --- a/plugins/tags-resources/src/components/TagsPopup.svelte +++ b/plugins/tags-resources/src/components/TagsPopup.svelte @@ -16,7 +16,7 @@ import { Class, Doc, Ref } from '@hcengineering/core' import type { IntlString } from '@hcengineering/platform' import presentation, { createQuery, getClient } from '@hcengineering/presentation' - import { findTagCategory, TagCategory, TagElement } from '@hcengineering/tags' + import { TagCategory, TagElement, findTagCategory } from '@hcengineering/tags' import { Button, EditWithIcon, @@ -33,10 +33,10 @@ } from '@hcengineering/ui' import { createEventDispatcher } from 'svelte' import tags from '../plugin' + import { createTagElement } from '../utils' import CreateTagElement from './CreateTagElement.svelte' import IconView from './icons/View.svelte' import IconViewHide from './icons/ViewHide.svelte' - import { createTagElement } from '../utils' export let newElements: TagElement[] = [] export let targetClass: Ref> @@ -69,15 +69,12 @@ objects = newElements.concat(result) }) - async function onCreateTagElement (res: any): Promise { - if (res === null) return - setTimeout(() => { - const tag = objects.findLast((e) => e._id === res) - if (tag === undefined) return - selected = [...selected, tag._id] - dispatch('update', { action: 'add', tag }) - inProcess = false - }, 1) + async function onCreateTagElement (res: Ref | undefined | null): Promise { + if (res == null) return + const tag = await getClient().findOne(tags.class.TagElement, { _id: res }) + dispatch('update', { action: 'add', tag }) + selected = [...selected, res] + inProcess = false } async function createTagElementPopup (): Promise { diff --git a/plugins/tags-resources/src/utils.ts b/plugins/tags-resources/src/utils.ts index f5bae1e1dc..f06a17c7f2 100644 --- a/plugins/tags-resources/src/utils.ts +++ b/plugins/tags-resources/src/utils.ts @@ -1,22 +1,14 @@ // Copyright © 2022 Hardcore Engineering Inc. -import { - type Class, - type Data, - type Doc, - type DocumentQuery, - type FindResult, - generateId, - type Ref -} from '@hcengineering/core' +import { type Class, type Data, type Doc, type DocumentQuery, type FindResult, type Ref } from '@hcengineering/core' import { type Asset } from '@hcengineering/platform' -import { type TagElement, type InitialKnowledge, type TagReference, type TagCategory } from '@hcengineering/tags' +import { getClient } from '@hcengineering/presentation' +import { type InitialKnowledge, type TagCategory, type TagElement, type TagReference } from '@hcengineering/tags' import { type ColorDefinition, getColorNumberByText } from '@hcengineering/ui' import { type Filter } from '@hcengineering/view' import { FilterQuery } from '@hcengineering/view-resources' -import tags from './plugin' import { writable } from 'svelte/store' -import { getClient } from '@hcengineering/presentation' +import tags from './plugin' export function getTagStyle (color: ColorDefinition, selected = false): string { return ` @@ -79,7 +71,7 @@ export async function createTagElement ( category?: Ref | null, description?: string | null, color?: number | null -): Promise { +): Promise> { const tagElement: Data = { title, description: description ?? '', @@ -89,6 +81,5 @@ export async function createTagElement ( } const client = getClient() - const tagElementId = generateId() - return await client.createDoc(tags.class.TagElement, tags.space.Tags, tagElement, tagElementId) + return await client.createDoc(tags.class.TagElement, tags.space.Tags, tagElement) } diff --git a/plugins/task-resources/src/components/AssignedTasks.svelte b/plugins/task-resources/src/components/AssignedTasks.svelte index e26b0b30bb..02f00fb6fb 100644 --- a/plugins/task-resources/src/components/AssignedTasks.svelte +++ b/plugins/task-resources/src/components/AssignedTasks.svelte @@ -72,8 +72,15 @@ let preference: ViewletPreference | undefined let documentIds: Ref[] = [] - function updateResultQuery (search: string, documentIds: Ref[], doneStates: Status[]): void { - resultQuery.status = { $nin: doneStates.map((it) => it._id) } + function updateResultQuery ( + search: string, + documentIds: Ref[], + doneStates: Status[], + mode: string | undefined + ): void { + if (mode === 'assigned') { + resultQuery.status = { $nin: doneStates.map((it) => it._id) } + } if (documentIds.length > 0) { resultQuery._id = { $in: documentIds } } @@ -123,7 +130,7 @@ } } - $: updateResultQuery(search, documentIds, doneStates) + $: updateResultQuery(search, documentIds, doneStates, mode) let viewlet: Viewlet | undefined @@ -155,7 +162,7 @@ { - updateResultQuery(search, documentIds, doneStates) + updateResultQuery(search, documentIds, doneStates, mode) }} />
diff --git a/plugins/task-resources/src/components/StatusTableView.svelte b/plugins/task-resources/src/components/StatusTableView.svelte index 9c92ade666..659830789c 100644 --- a/plugins/task-resources/src/components/StatusTableView.svelte +++ b/plugins/task-resources/src/components/StatusTableView.svelte @@ -83,7 +83,7 @@ const client = getClient() - async function updateQuery (query: DocumentQuery, selectedDoneStates: Set>): Promise { + function updateQuery (query: DocumentQuery, selectedDoneStates: Set>): void { resConfig = updateConfig(config) const result = client.getHierarchy().clone(query) if (state) { @@ -164,7 +164,14 @@ {:else} - updateQuery(query, selectedDoneStates)} /> + { + updateQuery(query, selectedDoneStates) + }} + /> {/if}
diff --git a/plugins/task-resources/src/components/state/StateIconPresenter.svelte b/plugins/task-resources/src/components/state/StateIconPresenter.svelte index d8222d5c11..3db38bcf6e 100644 --- a/plugins/task-resources/src/components/state/StateIconPresenter.svelte +++ b/plugins/task-resources/src/components/state/StateIconPresenter.svelte @@ -14,7 +14,7 @@ --> diff --git a/plugins/tracker-resources/src/components/issues/IssueStatusActivity.svelte b/plugins/tracker-resources/src/components/issues/IssueStatusActivity.svelte index 6611806e31..92422b6ad3 100644 --- a/plugins/tracker-resources/src/components/issues/IssueStatusActivity.svelte +++ b/plugins/tracker-resources/src/components/issues/IssueStatusActivity.svelte @@ -11,7 +11,7 @@ } from '@hcengineering/core' import { createQuery } from '@hcengineering/presentation' import { Issue, IssueStatus } from '@hcengineering/tracker' - import { Label, ticker, Row } from '@hcengineering/ui' + import { Label, Row, ticker } from '@hcengineering/ui' import { statusStore } from '@hcengineering/view-resources' import tracker from '../../plugin' import Duration from './Duration.svelte' @@ -39,7 +39,7 @@ ) let displaySt: WithTime[] = [] - async function updateStatus (txes: Tx[], statuses: IdMap, _: number): Promise { + function updateStatus (txes: Tx[], statuses: IdMap, _: number): void { const result: WithTime[] = [] let current: Ref | undefined diff --git a/plugins/tracker-resources/src/components/milestones/MilestoneSelector.svelte b/plugins/tracker-resources/src/components/milestones/MilestoneSelector.svelte index 92630ef03d..351f4b8764 100644 --- a/plugins/tracker-resources/src/components/milestones/MilestoneSelector.svelte +++ b/plugins/tracker-resources/src/components/milestones/MilestoneSelector.svelte @@ -17,7 +17,7 @@ import { IntlString, getEmbeddedLabel, translate } from '@hcengineering/platform' import { createQuery } from '@hcengineering/presentation' import { Milestone } from '@hcengineering/tracker' - import type { ButtonKind, ButtonSize, LabelAndProps } from '@hcengineering/ui' + import type { ButtonKind, ButtonSize, LabelAndProps, PopupResult } from '@hcengineering/ui' import { Button, ButtonShape, Label, SelectPopup, eventToHTMLElement, showPopup, themeStore } from '@hcengineering/ui' import tracker from '../../plugin' import { milestoneStatusAssets } from '../../types' @@ -96,19 +96,25 @@ $: milestones = getMilestoneInfo(rawMilestones, selectedMilestone) + let milestonePopup: PopupResult | undefined const handleMilestoneEditorOpened = async (event: MouseEvent): Promise => { event.stopPropagation() if (!isEditable) { return } - showPopup( + milestonePopup = showPopup( SelectPopup, { value: milestones, placeholder: popupPlaceholder, searchable: true }, eventToHTMLElement(event), - onChange + (evt) => { + onChange?.(evt) + milestonePopup = undefined + } ) } + + $: milestonePopup?.update({ value: milestones }) {#if isAction} diff --git a/plugins/view-resources/src/components/ActionHandler.svelte b/plugins/view-resources/src/components/ActionHandler.svelte index 183d1398e8..c9ce50a76f 100644 --- a/plugins/view-resources/src/components/ActionHandler.svelte +++ b/plugins/view-resources/src/components/ActionHandler.svelte @@ -16,7 +16,7 @@ import { Analytics } from '@hcengineering/analytics' import core, { Doc, Hierarchy, Ref, Space, TxRemoveDoc } from '@hcengineering/core' import { getResource } from '@hcengineering/platform' - import { addTxListener, contextStore, getClient } from '@hcengineering/presentation' + import { addTxListener, contextStore, getClient, reduceCalls } from '@hcengineering/presentation' import { AnyComponent, Component } from '@hcengineering/ui' import { Action, ViewContextType } from '@hcengineering/view' import { fly } from 'svelte/transition' @@ -237,11 +237,12 @@ } let presenter: AnyComponent | undefined - async function updatePreviewPresenter (doc?: Doc): Promise { - presenter = doc !== undefined ? await getObjectPreview(client, Hierarchy.mixinOrClass(doc)) : undefined - } + const updatePreviewPresenter = reduceCalls(async function (doc?: Doc): Promise { + const r = doc !== undefined ? await getObjectPreview(client, Hierarchy.mixinOrClass(doc)) : undefined + presenter = r + }) - $: updatePreviewPresenter($previewDocument) + $: void updatePreviewPresenter($previewDocument) diff --git a/plugins/view-resources/src/components/ActionsPopup.svelte b/plugins/view-resources/src/components/ActionsPopup.svelte index 51c673292d..f9047ed479 100644 --- a/plugins/view-resources/src/components/ActionsPopup.svelte +++ b/plugins/view-resources/src/components/ActionsPopup.svelte @@ -13,42 +13,55 @@ // limitations under the License. --> diff --git a/plugins/workbench-resources/src/components/Workbench.svelte b/plugins/workbench-resources/src/components/Workbench.svelte index 803f927aeb..ae42423978 100644 --- a/plugins/workbench-resources/src/components/Workbench.svelte +++ b/plugins/workbench-resources/src/components/Workbench.svelte @@ -20,7 +20,14 @@ import notification, { DocNotifyContext, InboxNotification, notificationId } from '@hcengineering/notification' import { BrowserNotificatator, InboxNotificationsClientImpl } from '@hcengineering/notification-resources' import { IntlString, broadcastEvent, getMetadata, getResource } from '@hcengineering/platform' - import { ActionContext, ComponentExtensions, createQuery, getClient, isAdminUser } from '@hcengineering/presentation' + import { + ActionContext, + ComponentExtensions, + createQuery, + getClient, + isAdminUser, + reduceCalls + } from '@hcengineering/presentation' import setting from '@hcengineering/setting' import support, { SupportStatus, supportLink } from '@hcengineering/support' import { @@ -163,18 +170,17 @@ let hasNotificationsFn: ((data: Map, InboxNotification[]>) => Promise) | undefined = undefined let hasInboxNotifications = false - let syncPromise: Promise | undefined = undefined - let locUpdate = 0 - getResource(notification.function.HasInboxNotifications).then((f) => { + void getResource(notification.function.HasInboxNotifications).then((f) => { hasNotificationsFn = f }) - $: hasNotificationsFn?.($inboxNotificationsByContextStore).then((res) => { + $: void hasNotificationsFn?.($inboxNotificationsByContextStore).then((res) => { hasInboxNotifications = res }) - const doSyncLoc = async (loc: Location, iteration: number): Promise => { + const doSyncLoc = reduceCalls(async (loc: Location): Promise => { + console.log('do sync', JSON.stringify(loc), $location.path) if (workspaceId !== $location.path[1]) { // Switch of workspace return @@ -182,19 +188,15 @@ closeTooltip() closePopup() - await syncLoc(loc, iteration) + await syncLoc(loc) await updateWindowTitle(loc) checkOnHide() - syncPromise = undefined - } + console.log('do sync-end', JSON.stringify(loc), $location.path) + }) + onDestroy( location.subscribe((loc) => { - locUpdate++ - if (syncPromise !== undefined) { - void syncPromise.then(() => doSyncLoc(loc, locUpdate)) - } else { - syncPromise = doSyncLoc(loc, locUpdate) - } + void doSyncLoc(loc) }) ) @@ -294,15 +296,12 @@ return loc } - async function syncLoc (loc: Location, iteration: number): Promise { + async function syncLoc (loc: Location): Promise { const originalLoc = JSON.stringify(loc) if (loc.path.length > 3 && getSpecialComponent(loc.path[3]) === undefined) { // resolve short links const resolvedLoc = await resolveShortLink(loc) - if (locUpdate !== iteration) { - return - } if (resolvedLoc !== undefined && !areLocationsEqual(loc, resolvedLoc.loc)) { loc = mergeLoc(loc, resolvedLoc) } @@ -334,9 +333,6 @@ let len = 3 if (spaceRef !== undefined && specialRef !== undefined) { const spaceObj = await client.findOne(core.class.Space, { _id: spaceRef }) - if (locUpdate !== iteration) { - return - } if (spaceObj !== undefined) { loc.path[3] = spaceRef loc.path[4] = specialRef @@ -355,13 +351,7 @@ clear(1) currentAppAlias = app currentApplication = await client.findOne(workbench.class.Application, { alias: app }) - if (locUpdate !== iteration) { - return - } navigatorModel = await buildNavModel(client, currentApplication) - if (locUpdate !== iteration) { - return - } } if ( @@ -395,9 +385,6 @@ currentSpecial = space } else { await updateSpace(space) - if (locUpdate !== iteration) { - return - } setSpaceSpecial(special) } } @@ -425,6 +412,7 @@ if (props.length >= 3) { const doc = await client.findOne(props[2] as Ref>, { _id: props[1] as Ref }) + if (doc !== undefined) { const provider = ListSelectionProvider.Find(doc._id) updateFocus({ diff --git a/plugins/workbench-resources/src/components/icons/TodoCheck.svelte b/plugins/workbench-resources/src/components/icons/TodoCheck.svelte new file mode 100644 index 0000000000..34cf517908 --- /dev/null +++ b/plugins/workbench-resources/src/components/icons/TodoCheck.svelte @@ -0,0 +1,11 @@ + + + + + + diff --git a/plugins/workbench-resources/src/components/icons/TodoUncheck.svelte b/plugins/workbench-resources/src/components/icons/TodoUncheck.svelte new file mode 100644 index 0000000000..4d622a41c0 --- /dev/null +++ b/plugins/workbench-resources/src/components/icons/TodoUncheck.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/server/account/src/index.ts b/server/account/src/index.ts index dee77a00a7..94b4595801 100644 --- a/server/account/src/index.ts +++ b/server/account/src/index.ts @@ -877,7 +877,13 @@ export async function createWorkspace ( try { const initWS = getMetadata(toolPlugin.metadata.InitWorkspace) const wsId = getWorkspaceId(workspaceInfo.workspace, productId) - if (initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null) { + + // We should not try to clone INIT_WS into INIT_WS during it's creation. + if ( + initWS !== undefined && + (await getWorkspaceById(db, productId, initWS)) !== null && + initWS !== workspaceInfo.workspace + ) { // Just any valid model for transactor to be able to function await ( await initModel(ctx, getTransactor(), wsId, txes, [], ctxModellogger, async (value) => { diff --git a/server/core/src/fulltext.ts b/server/core/src/fulltext.ts index 6a26dd923a..89ffac5817 100644 --- a/server/core/src/fulltext.ts +++ b/server/core/src/fulltext.ts @@ -207,10 +207,19 @@ export class FullTextIndex implements WithFind { const indexedDocMap = new Map, IndexedDoc>() for (const doc of docs) { - if (doc._class.some((cl) => this.hierarchy.isDerived(cl, baseClass))) { + if ( + doc._class != null && + Array.isArray(doc._class) && + doc._class.some((cl) => this.hierarchy.isDerived(cl, baseClass)) + ) { ids.add(doc.id) indexedDocMap.set(doc.id, doc) } + if (doc._class !== null && !Array.isArray(doc._class) && this.hierarchy.isDerived(doc._class, baseClass)) { + ids.add(doc.id) + indexedDocMap.set(doc.id, doc) + } + if (doc.attachedTo != null) { if (doc.attachedToClass != null && this.hierarchy.isDerived(doc.attachedToClass, baseClass)) { if (this.hierarchy.isDerived(doc.attachedToClass, baseClass)) { diff --git a/server/tool/src/index.ts b/server/tool/src/index.ts index a47e7bf88b..ef10ca7aa5 100644 --- a/server/tool/src/index.ts +++ b/server/tool/src/index.ts @@ -245,19 +245,20 @@ export async function upgradeModel ( const migrateClient = new MigrateClientImpl(db, hierarchy, modelDb, logger) const states = await migrateClient.find(DOMAIN_MIGRATION, { _class: core.class.MigrationState }) - const migrateState = new Map( - Array.from(groupByArray(states, (it) => it.plugin).entries()).map((it) => [ - it[0], - new Set(it[1].map((q) => q.state)) - ]) - ) + const sts = Array.from(groupByArray(states, (it) => it.plugin).entries()) + const migrateState = new Map(sts.map((it) => [it[0], new Set(it[1].map((q) => q.state))])) migrateClient.migrateState = migrateState await ctx.with('migrate', {}, async () => { let i = 0 for (const op of migrateOperations) { const t = Date.now() - await op[1].migrate(migrateClient, logger) + try { + await op[1].migrate(migrateClient, logger) + } catch (err: any) { + logger.error(`error during migrate: ${op[0]} ${err.message}`, err) + throw err + } logger.log('migrate:', { workspaceId: workspaceId.name, operation: op[0], time: Date.now() - t }) await progress(20 + ((100 / migrateOperations.length) * i * 20) / 100) i++ diff --git a/tests/sanity/tests/collaborative/applications.spec.ts b/tests/sanity/tests/collaborative/applications.spec.ts index 667106e55f..7883d2b3c1 100644 --- a/tests/sanity/tests/collaborative/applications.spec.ts +++ b/tests/sanity/tests/collaborative/applications.spec.ts @@ -19,73 +19,78 @@ test.describe('Collaborative tests for Application', () => { const vacancyName = 'Software Engineer' let talentName: TalentName // open second page - const userSecondPage = await getSecondPage(browser) + const { page: userSecondPage, context } = await getSecondPage(browser) - await test.step('User1. Add collaborators and comment from user1', async () => { - const navigationMenuPage = new NavigationMenuPage(page) - await navigationMenuPage.buttonApplications.click() + try { + await test.step('User1. Add collaborators and comment from user1', async () => { + const navigationMenuPage = new NavigationMenuPage(page) + await navigationMenuPage.buttonApplications.click() - const applicationsPage = new ApplicationsPage(page) - talentName = await applicationsPage.createNewApplicationWithNewTalent({ - vacancy: vacancyName, - recruiterName: 'first' + const applicationsPage = new ApplicationsPage(page) + talentName = await applicationsPage.createNewApplicationWithNewTalent({ + vacancy: vacancyName, + recruiterName: 'first' + }) + await applicationsPage.openApplicationByTalentName(talentName) + + const applicationsDetailsPage = new ApplicationsDetailsPage(page) + await applicationsDetailsPage.addCollaborators('Dirak Kainin') + await applicationsDetailsPage.addComment('Test Comment from user1') + await applicationsDetailsPage.checkCommentExist('Test Comment from user1') }) - await applicationsPage.openApplicationByTalentName(talentName) - const applicationsDetailsPage = new ApplicationsDetailsPage(page) - await applicationsDetailsPage.addCollaborators('Dirak Kainin') - await applicationsDetailsPage.addComment('Test Comment from user1') - await applicationsDetailsPage.checkCommentExist('Test Comment from user1') - }) + await test.step('User2. Check notification and add comment from user2', async () => { + await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/recruit`))?.finished() - await test.step('User2. Check notification and add comment from user2', async () => { - await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/recruit`))?.finished() + const leftSideMenuPageSecond = new LeftSideMenuPage(userSecondPage) + await leftSideMenuPageSecond.checkExistNewNotification(userSecondPage) + await leftSideMenuPageSecond.buttonNotification.click() - const leftSideMenuPageSecond = new LeftSideMenuPage(userSecondPage) - await leftSideMenuPageSecond.checkExistNewNotification(userSecondPage) - await leftSideMenuPageSecond.buttonNotification.click() + // TODO: rewrite checkNotificationCollaborators and uncomment + // const notificationPageSecond = new NotificationPage(userSecondPage) + // await notificationPageSecond.checkNotificationCollaborators( + // `${talentName.lastName} ${talentName.firstName}`, + // 'You have been added to collaborators' + // ) - // TODO: rewrite checkNotificationCollaborators and uncomment - // const notificationPageSecond = new NotificationPage(userSecondPage) - // await notificationPageSecond.checkNotificationCollaborators( - // `${talentName.lastName} ${talentName.firstName}`, - // 'You have been added to collaborators' - // ) + await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/recruit`))?.finished() + const navigationMenuPageSecond = new NavigationMenuPage(userSecondPage) + await navigationMenuPageSecond.buttonApplications.click() - await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/recruit`))?.finished() - const navigationMenuPageSecond = new NavigationMenuPage(userSecondPage) - await navigationMenuPageSecond.buttonApplications.click() + const applicationsPageSecond = new ApplicationsPage(userSecondPage) + await applicationsPageSecond.openApplicationByTalentName(talentName) - const applicationsPageSecond = new ApplicationsPage(userSecondPage) - await applicationsPageSecond.openApplicationByTalentName(talentName) + const applicationsDetailsPageSecond = new ApplicationsDetailsPage(userSecondPage) + await applicationsDetailsPageSecond.checkCommentExist('Test Comment from user1') + await applicationsDetailsPageSecond.addComment('Test Comment from user2') + await applicationsDetailsPageSecond.checkCommentExist('Test Comment from user2') + }) - const applicationsDetailsPageSecond = new ApplicationsDetailsPage(userSecondPage) - await applicationsDetailsPageSecond.checkCommentExist('Test Comment from user1') - await applicationsDetailsPageSecond.addComment('Test Comment from user2') - await applicationsDetailsPageSecond.checkCommentExist('Test Comment from user2') - }) + await test.step('User1. Check notification and check comment from user1', async () => { + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.checkExistNewNotification(page) + await leftSideMenuPage.buttonNotification.click() - await test.step('User1. Check notification and check comment from user1', async () => { - const leftSideMenuPage = new LeftSideMenuPage(page) - await leftSideMenuPage.checkExistNewNotification(page) - await leftSideMenuPage.buttonNotification.click() + // TODO: rewrite checkNotificationCollaborators and uncomment + // const notificationPage = new NotificationPage(page) + // await notificationPage.checkNotificationCollaborators( + // `${talentName.lastName} ${talentName.firstName}`, + // 'left a comment' + // ) - // TODO: rewrite checkNotificationCollaborators and uncomment - // const notificationPage = new NotificationPage(page) - // await notificationPage.checkNotificationCollaborators( - // `${talentName.lastName} ${talentName.firstName}`, - // 'left a comment' - // ) + await (await page.goto(`${PlatformURI}/workbench/sanity-ws/recruit`))?.finished() + const navigationMenuPage = new NavigationMenuPage(page) + await navigationMenuPage.buttonApplications.click() - await (await page.goto(`${PlatformURI}/workbench/sanity-ws/recruit`))?.finished() - const navigationMenuPage = new NavigationMenuPage(page) - await navigationMenuPage.buttonApplications.click() + const applicationsPage = new ApplicationsPage(page) + await applicationsPage.openApplicationByTalentName(talentName) - const applicationsPage = new ApplicationsPage(page) - await applicationsPage.openApplicationByTalentName(talentName) - - const applicationsDetailsPage = new ApplicationsDetailsPage(page) - await applicationsDetailsPage.checkCommentExist('Test Comment from user2') - }) + const applicationsDetailsPage = new ApplicationsDetailsPage(page) + await applicationsDetailsPage.checkCommentExist('Test Comment from user2') + }) + } finally { + await userSecondPage.close() + await context.close() + } }) }) diff --git a/tests/sanity/tests/collaborative/issues.spec.ts b/tests/sanity/tests/collaborative/issues.spec.ts index 04ceebc3b7..159c0eaf68 100644 --- a/tests/sanity/tests/collaborative/issues.spec.ts +++ b/tests/sanity/tests/collaborative/issues.spec.ts @@ -1,9 +1,9 @@ import { test } from '@playwright/test' -import { generateId, getSecondPage, PlatformSetting, PlatformURI } from '../utils' -import { NewIssue } from '../model/tracker/types' -import { IssuesPage } from '../model/tracker/issues-page' import { LeftSideMenuPage } from '../model/left-side-menu-page' import { IssuesDetailsPage } from '../model/tracker/issues-details-page' +import { IssuesPage } from '../model/tracker/issues-page' +import { NewIssue } from '../model/tracker/types' +import { generateId, getSecondPage, PlatformSetting, PlatformURI } from '../utils' test.use({ storageState: PlatformSetting @@ -22,7 +22,7 @@ test.describe('Collaborative test for issue', () => { priority: 'Urgent', assignee: 'Appleseed John', createLabel: true, - labels: `CREATE-ISSUE-${generateId()}`, + labels: `CREATE-ISSUE-${generateId(5)}`, component: 'No component', estimation: '2', milestone: 'No Milestone', @@ -31,38 +31,42 @@ test.describe('Collaborative test for issue', () => { } // open second page - const userSecondPage = await getSecondPage(browser) - await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() + const { page: userSecondPage, context } = await getSecondPage(browser) + try { + await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() - // create a new issue by first user - await (await page.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() - const leftSideMenuPage = new LeftSideMenuPage(page) - await leftSideMenuPage.buttonTracker.click() + // create a new issue by first user + await (await page.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonTracker.click() - const issuesPage = new IssuesPage(page) - await issuesPage.createNewIssue(newIssue) - await issuesPage.linkSidebarAll.click() - await issuesPage.modelSelectorAll.click() - await issuesPage.searchIssueByName(newIssue.title) - await issuesPage.openIssueByName(newIssue.title) + const issuesPage = new IssuesPage(page) + await issuesPage.createNewIssue(newIssue) + await issuesPage.linkSidebarAll.click() + await issuesPage.modelSelectorAll.click() + await issuesPage.searchIssueByName(newIssue.title) + await issuesPage.openIssueByName(newIssue.title) - // check created issued by second user - const issuesPageSecond = new IssuesPage(userSecondPage) - await userSecondPage.evaluate(() => { - localStorage.setItem('platform.activity.threshold', '0') - }) - await issuesPageSecond.linkSidebarAll.click() - await issuesPageSecond.modelSelectorAll.click() - await issuesPageSecond.searchIssueByName(newIssue.title) - await issuesPageSecond.openIssueByName(newIssue.title) + // check created issued by second user + const issuesPageSecond = new IssuesPage(userSecondPage) + await userSecondPage.evaluate(() => { + localStorage.setItem('platform.activity.threshold', '0') + }) + await issuesPageSecond.linkSidebarAll.click() + await issuesPageSecond.modelSelectorAll.click() + await issuesPageSecond.searchIssueByName(newIssue.title) + await issuesPageSecond.openIssueByName(newIssue.title) - const issuesDetailsPageSecond = new IssuesDetailsPage(userSecondPage) - await issuesDetailsPageSecond.checkIssue({ - ...newIssue, - milestone: 'Milestone', - estimation: '2h' - }) - await userSecondPage.close() + const issuesDetailsPageSecond = new IssuesDetailsPage(userSecondPage) + await issuesDetailsPageSecond.checkIssue({ + ...newIssue, + milestone: 'Milestone', + estimation: '2h' + }) + } finally { + await userSecondPage.close() + await context.close() + } }) test('Issues status can be changed by another users', async ({ page, browser }) => { @@ -72,42 +76,48 @@ test.describe('Collaborative test for issue', () => { } // open second page - const userSecondPage = await getSecondPage(browser) - await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() + const { page: userSecondPage, context } = await getSecondPage(browser) - const issuesPageSecond = new IssuesPage(userSecondPage) - await issuesPageSecond.linkSidebarAll.click() - await issuesPageSecond.modelSelectorAll.click() + try { + if (userSecondPage.url() !== `${PlatformURI}/workbench/sanity-ws/tracker/`) { + await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() + } - // change status - await (await page.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() - const issuesPage = new IssuesPage(page) - await issuesPage.linkSidebarAll.click() - await issuesPage.modelSelectorBacklog.click() - await issuesPage.searchIssueByName(issue.title) - await issuesPage.openIssueByName(issue.title) + const issuesPageSecond = new IssuesPage(userSecondPage) + await issuesPageSecond.linkSidebarAll.click() + await issuesPageSecond.modelSelectorAll.click() - const issuesDetailsPage = new IssuesDetailsPage(page) - await issuesDetailsPage.editIssue({ status: 'In Progress' }) + // change status + const issuesPage = new IssuesPage(page) + await issuesPage.linkSidebarAll.click() + await issuesPage.modelSelectorBacklog.click() + await issuesPage.searchIssueByName(issue.title) + await issuesPage.openIssueByName(issue.title) - // check by another user - await issuesPageSecond.modelSelectorBacklog.click() - // not active for another user - await issuesPageSecond.checkIssueNotExist(issue.title) + const issuesDetailsPage = new IssuesDetailsPage(page) + await issuesDetailsPage.editIssue({ status: 'In Progress' }) - await issuesPageSecond.modelSelectorActive.click() - await issuesPageSecond.searchIssueByName(issue.title) - await issuesPageSecond.openIssueByName(issue.title) + // check by another user + await issuesPageSecond.modelSelectorBacklog.click() + // not active for another user + await issuesPageSecond.checkIssueNotExist(issue.title) - const issuesDetailsPageSecond = new IssuesDetailsPage(userSecondPage) - await userSecondPage.evaluate(() => { - localStorage.setItem('platform.activity.threshold', '0') - }) - await issuesDetailsPageSecond.checkIssue({ - ...issue, - status: 'In Progress' - }) - await userSecondPage.close() + await issuesPageSecond.modelSelectorActive.click() + await issuesPageSecond.searchIssueByName(issue.title) + await issuesPageSecond.openIssueByName(issue.title) + + const issuesDetailsPageSecond = new IssuesDetailsPage(userSecondPage) + await userSecondPage.evaluate(() => { + localStorage.setItem('platform.activity.threshold', '0') + }) + await issuesDetailsPageSecond.checkIssue({ + ...issue, + status: 'In Progress' + }) + } finally { + await userSecondPage.close() + await context.close() + } }) test('First user change assignee, second user should see assigned issue', async ({ page, browser }) => { @@ -118,44 +128,48 @@ test.describe('Collaborative test for issue', () => { } // open second page - const userSecondPage = await getSecondPage(browser) - await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() + const { page: userSecondPage, context } = await getSecondPage(browser) + try { + await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() - await test.step(`user1. change assignee to ${newAssignee}`, async () => { - await (await page.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() - const issuesPage = new IssuesPage(page) - await issuesPage.linkSidebarAll.click() - await issuesPage.modelSelectorBacklog.click() - await issuesPage.searchIssueByName(issue.title) - await issuesPage.openIssueByName(issue.title) + await test.step(`user1. change assignee to ${newAssignee}`, async () => { + await (await page.goto(`${PlatformURI}/workbench/sanity-ws/tracker/`))?.finished() + const issuesPage = new IssuesPage(page) + await issuesPage.linkSidebarAll.click() + await issuesPage.modelSelectorBacklog.click() + await issuesPage.searchIssueByName(issue.title) + await issuesPage.openIssueByName(issue.title) - const issuesDetailsPage = new IssuesDetailsPage(page) - await issuesDetailsPage.editIssue({ assignee: newAssignee }) - }) + const issuesDetailsPage = new IssuesDetailsPage(page) + await issuesDetailsPage.editIssue({ assignee: newAssignee }) + }) - // TODO: rewrite checkNotificationIssue and uncomment - // await test.step('user2. check notification', async () => { - // const leftSideMenuPageSecond = new LeftSideMenuPage(userSecondPage) - // await leftSideMenuPageSecond.checkExistNewNotification(userSecondPage) - // await leftSideMenuPageSecond.buttonNotification.click() - // - // const notificationPageSecond = new NotificationPage(userSecondPage) - // await notificationPageSecond.checkNotificationIssue(issue.title, newAssignee) - // - // await leftSideMenuPageSecond.buttonTracker.click() - // }) + // TODO: rewrite checkNotificationIssue and uncomment + // await test.step('user2. check notification', async () => { + // const leftSideMenuPageSecond = new LeftSideMenuPage(userSecondPage) + // await leftSideMenuPageSecond.checkExistNewNotification(userSecondPage) + // await leftSideMenuPageSecond.buttonNotification.click() + // + // const notificationPageSecond = new NotificationPage(userSecondPage) + // await notificationPageSecond.checkNotificationIssue(issue.title, newAssignee) + // + // await leftSideMenuPageSecond.buttonTracker.click() + // }) - await test.step('user2. check issue assignee', async () => { - const issuesPageSecond = new IssuesPage(userSecondPage) - await issuesPageSecond.linkSidebarMyIssue.click() - await issuesPageSecond.modelSelectorBacklog.click() + await test.step('user2. check issue assignee', async () => { + const issuesPageSecond = new IssuesPage(userSecondPage) + await issuesPageSecond.linkSidebarMyIssue.click() + await issuesPageSecond.modelSelectorBacklog.click() - await issuesPageSecond.searchIssueByName(issue.title) - await issuesPageSecond.openIssueByName(issue.title) + await issuesPageSecond.searchIssueByName(issue.title) + await issuesPageSecond.openIssueByName(issue.title) - const issuesDetailsPageSecond = new IssuesDetailsPage(userSecondPage) - await issuesDetailsPageSecond.checkIssue({ ...issue }) - }) - await userSecondPage.close() + const issuesDetailsPageSecond = new IssuesDetailsPage(userSecondPage) + await issuesDetailsPageSecond.checkIssue({ ...issue }) + }) + } finally { + await userSecondPage.close() + await context.close() + } }) }) diff --git a/tests/sanity/tests/documents/documents-link.spec.ts b/tests/sanity/tests/documents/documents-link.spec.ts index a7dc577d15..bd6ab40d7d 100644 --- a/tests/sanity/tests/documents/documents-link.spec.ts +++ b/tests/sanity/tests/documents/documents-link.spec.ts @@ -1,10 +1,10 @@ import { expect, test } from '@playwright/test' -import { generateId, PlatformSetting, PlatformURI } from '../utils' +import { DocumentContentPage } from '../model/documents/document-content-page' +import { DocumentsPage } from '../model/documents/documents-page' import { NewDocument } from '../model/documents/types' import { LeftSideMenuPage } from '../model/left-side-menu-page' -import { DocumentsPage } from '../model/documents/documents-page' -import { DocumentContentPage } from '../model/documents/document-content-page' import { PublicLinkPopup } from '../model/tracker/public-link-popup' +import { generateId, PlatformSetting, PlatformURI } from '../utils' test.describe('Documents link tests', () => { test('Document public link revoke', async ({ browser }) => { @@ -15,46 +15,56 @@ test.describe('Documents link tests', () => { const newContext = await browser.newContext({ storageState: PlatformSetting }) const page = await newContext.newPage() - await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() + try { + await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() - const leftSideMenuPage = new LeftSideMenuPage(page) - await leftSideMenuPage.buttonDocuments.click() + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonDocuments.click() - const documentsPage = new DocumentsPage(page) - await documentsPage.buttonCreateDocument.click() + const documentsPage = new DocumentsPage(page) + await documentsPage.buttonCreateDocument.click() - await documentsPage.createDocument(publicLinkDocument) - await documentsPage.openDocument(publicLinkDocument.title) + await documentsPage.createDocument(publicLinkDocument) + await documentsPage.openDocument(publicLinkDocument.title) - const documentContentPage = new DocumentContentPage(page) - await documentContentPage.executeMoreAction('Public link') + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.executeMoreAction('Public link') - // remove after UBERF-5994 fixed - await documentContentPage.closePopup(page) - await page.reload({ waitUntil: 'commit' }) - await documentContentPage.executeMoreAction('Public link') + // remove after UBERF-5994 fixed + await documentContentPage.closePopup(page) + await page.reload({ waitUntil: 'commit' }) + await documentContentPage.executeMoreAction('Public link') - const publicLinkPopup = new PublicLinkPopup(page) - const link = await publicLinkPopup.getPublicLink() - - const clearSession = await browser.newContext() - const clearPage = await clearSession.newPage() - await test.step('Check guest access to the document', async () => { - await clearPage.goto(link) - - const documentContentClearPage = new DocumentContentPage(clearPage) - await documentContentClearPage.checkDocumentTitle(publicLinkDocument.title) - expect(clearPage.url()).toContain('guest') - }) - - await test.step('Revoke guest access to the document', async () => { const publicLinkPopup = new PublicLinkPopup(page) - await publicLinkPopup.revokePublicLink() - }) + const link = await publicLinkPopup.getPublicLink() - await test.step('Check guest access to the document after the revoke', async () => { - await clearPage.goto(link) - await expect(clearPage.locator('div.antiPopup > h1')).toHaveText('Public link was revoked') - }) + const clearSession = await browser.newContext() + const clearPage = await clearSession.newPage() + try { + await test.step('Check guest access to the document', async () => { + await clearPage.goto(link) + + const documentContentClearPage = new DocumentContentPage(clearPage) + await documentContentClearPage.checkDocumentTitle(publicLinkDocument.title) + expect(clearPage.url()).toContain('guest') + }) + + await test.step('Revoke guest access to the document', async () => { + const publicLinkPopup = new PublicLinkPopup(page) + await publicLinkPopup.revokePublicLink() + }) + + await test.step('Check guest access to the document after the revoke', async () => { + await clearPage.goto(link) + await expect(clearPage.locator('div.antiPopup > h1')).toHaveText('Public link was revoked') + }) + } finally { + await clearPage.close() + await clearSession.close() + } + } finally { + await page.close() + await newContext.close() + } }) }) diff --git a/tests/sanity/tests/documents/documents.spec.ts b/tests/sanity/tests/documents/documents.spec.ts index 4a63363bad..9d992acc71 100644 --- a/tests/sanity/tests/documents/documents.spec.ts +++ b/tests/sanity/tests/documents/documents.spec.ts @@ -130,22 +130,27 @@ test.describe('Documents tests', () => { }) await test.step('User2. Add content second user', async () => { - const userSecondPage = await getSecondPage(browser) - await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() + const { page: userSecondPage, context } = await getSecondPage(browser) + try { + await (await userSecondPage.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() - const leftSideMenuPageSecond = new LeftSideMenuPage(userSecondPage) - await leftSideMenuPageSecond.buttonDocuments.click() + const leftSideMenuPageSecond = new LeftSideMenuPage(userSecondPage) + await leftSideMenuPageSecond.buttonDocuments.click() - const documentsPageSecond = new DocumentsPage(userSecondPage) - await documentsPageSecond.openTeamspace(colDocument.space) - await documentsPageSecond.openDocument(colDocument.title) + const documentsPageSecond = new DocumentsPage(userSecondPage) + await documentsPageSecond.openTeamspace(colDocument.space) + await documentsPageSecond.openDocument(colDocument.title) - const documentContentPageSecond = new DocumentContentPage(page) - await documentContentPageSecond.checkDocumentTitle(colDocument.title) - await documentContentPageSecond.checkContent(content) + const documentContentPageSecond = new DocumentContentPage(page) + await documentContentPageSecond.checkDocumentTitle(colDocument.title) + await documentContentPageSecond.checkContent(content) - content = await documentContentPageSecond.addContentToTheNewLine(contentSecondUser) - await documentContentPageSecond.checkContent(content) + content = await documentContentPageSecond.addContentToTheNewLine(contentSecondUser) + await documentContentPageSecond.checkContent(content) + } finally { + await userSecondPage.close() + await context.close() + } }) await test.step('User1. Check final content', async () => { diff --git a/tests/sanity/tests/model/common-page.ts b/tests/sanity/tests/model/common-page.ts index 001e4cf203..3ffdb7f578 100644 --- a/tests/sanity/tests/model/common-page.ts +++ b/tests/sanity/tests/model/common-page.ts @@ -52,6 +52,7 @@ export class CommonPage { .locator('div.popup form[id="tags:string:AddTag"] input[placeholder="Please type description here"]') .fill(description) await page.locator('div.popup form[id="tags:string:AddTag"] button[type="submit"]').click() + await page.locator('div.popup form[id="tags:string:AddTag"]').waitFor({ state: 'hidden' }) } async selectAssignee (page: Page, name: string): Promise { diff --git a/tests/sanity/tests/model/documents/document-content-page.ts b/tests/sanity/tests/model/documents/document-content-page.ts index dd33420747..abf8ae9165 100644 --- a/tests/sanity/tests/model/documents/document-content-page.ts +++ b/tests/sanity/tests/model/documents/document-content-page.ts @@ -27,6 +27,7 @@ export class DocumentContentPage extends CommonPage { async addContentToTheNewLine (newContent: string): Promise { await expect(this.inputContent).toBeVisible() + await expect(this.inputContent).toHaveJSProperty('contentEditable', 'true') await this.inputContent.pressSequentially(`\n${newContent}`) const endContent = await this.inputContent.textContent() if (endContent == null) { @@ -42,6 +43,7 @@ export class DocumentContentPage extends CommonPage { async updateDocumentTitle (title: string): Promise { await this.buttonDocumentTitle.fill(title) + await this.buttonDocumentTitle.blur() } async addRandomLines (count: number, lineLength: number = 36): Promise { diff --git a/tests/sanity/tests/model/planning/planning-page.ts b/tests/sanity/tests/model/planning/planning-page.ts index 3e44f6f084..8b15962a99 100644 --- a/tests/sanity/tests/model/planning/planning-page.ts +++ b/tests/sanity/tests/model/planning/planning-page.ts @@ -117,9 +117,10 @@ export class PlanningPage extends CalendarPage { if (data.createLabel) { await this.pressCreateButtonSelectPopup(this.page) await this.addNewTagPopup(this.page, data.labels, 'Tag from createNewIssue') + await this.page.locator('.popup#TagsPopup').press('Escape') + } else { + await this.checkFromDropdownWithSearch(this.page, data.labels) } - await this.checkFromDropdownWithSearch(this.page, data.labels) - await (popup ? this.buttonPopupCreateAddLabel.press('Escape') : this.buttonPanelCreateAddLabel.press('Escape')) } if (data.slots != null) { let index = 0 diff --git a/tests/sanity/tests/model/tracker/issues-details-page.ts b/tests/sanity/tests/model/tracker/issues-details-page.ts index 5438c5e9f4..b3286284f1 100644 --- a/tests/sanity/tests/model/tracker/issues-details-page.ts +++ b/tests/sanity/tests/model/tracker/issues-details-page.ts @@ -31,7 +31,7 @@ export class IssuesDetailsPage extends CommonTrackerPage { super(page) this.page = page this.inputTitle = page.locator('div.popupPanel-body input[type="text"]') - this.inputDescription = page.locator('div.popupPanel-body div.textInput p') + this.inputDescription = page.locator('div.popupPanel-body div.textInput div.tiptap') this.buttonStatus = page.locator('//span[text()="Status"]/../button[1]//span') this.buttonPriority = page.locator('//span[text()="Priority"]/../button[2]//span') this.buttonAssignee = page.locator('(//span[text()="Assignee"]/../div/button)[2]') @@ -170,6 +170,7 @@ export class IssuesDetailsPage extends CommonTrackerPage { async addToDescription (description: string): Promise { const existDescription = await this.inputDescription.textContent() + await expect(this.inputDescription).toHaveJSProperty('contentEditable', 'true') await this.inputDescription.fill(`${existDescription}\n${description}`) } diff --git a/tests/sanity/tests/model/tracker/issues-page.ts b/tests/sanity/tests/model/tracker/issues-page.ts index c78775ba7a..0106db7873 100644 --- a/tests/sanity/tests/model/tracker/issues-page.ts +++ b/tests/sanity/tests/model/tracker/issues-page.ts @@ -110,11 +110,10 @@ export class IssuesPage extends CommonTrackerPage { if (data.createLabel) { await this.pressCreateButtonSelectPopup(this.page) await this.addNewTagPopup(this.page, data.labels, 'Tag from createNewIssue') + await this.page.locator('.popup#TagsPopup').press('Escape') } else { await this.checkFromDropdown(this.page, data.labels) } - await this.inputPopupCreateNewIssueTitle.press('Escape') - await this.inputPopupCreateNewIssueTitle.click({ force: true }) } if (data.component != null) { await this.buttonPopupCreateNewIssueComponent.click() @@ -211,10 +210,14 @@ export class IssuesPage extends CommonTrackerPage { } async checkAllIssuesByPriority (priorityName: string): Promise { - for await (const locator of iterateLocator(this.issuesList)) { - const href = await locator.locator('div.priority-container use').getAttribute('href') - expect(href).toContain(priorityName) - } + await expect(async () => { + for await (const locator of iterateLocator(this.issuesList)) { + const href = await locator.locator('div.priority-container use').getAttribute('href') + expect(href, { message: `Should contain ${priorityName} but it is ${href}` }).toContain(priorityName) + } + }).toPass({ + timeout: 15000 + }) } async getIssueId (issueLabel: string, position: number = 0): Promise { diff --git a/tests/sanity/tests/tracker/common-steps.ts b/tests/sanity/tests/tracker/common-steps.ts index 7c12f0aea1..c5a928193d 100644 --- a/tests/sanity/tests/tracker/common-steps.ts +++ b/tests/sanity/tests/tracker/common-steps.ts @@ -16,8 +16,8 @@ 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 () => { const issuesPage = new IssuesPage(page) + await issuesPage.linkSidebarAll.click() await issuesPage.modelSelectorAll.click() - await issuesPage.createNewIssue(issue) await issuesPage.searchIssueByName(issue.title) await issuesPage.openIssueByName(issue.title) diff --git a/tests/sanity/tests/tracker/component.spec.ts b/tests/sanity/tests/tracker/component.spec.ts index b1903912a2..45d8d9c636 100644 --- a/tests/sanity/tests/tracker/component.spec.ts +++ b/tests/sanity/tests/tracker/component.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test' -import { navigate } from './tracker.utils' import { generateId, PlatformSetting, PlatformURI, fillSearch } from '../utils' import { LeftSideMenuPage } from '../model/left-side-menu-page' import { TrackerNavigationMenuPage } from '../model/tracker/tracker-navigation-menu-page' @@ -18,8 +17,6 @@ test.describe('Tracker component tests', () => { test('create-component-issue', async ({ page }) => { await page.click('[id="app-tracker\\:string\\:TrackerApplication"]') - - await navigate(page) await page.click('text=Components') await expect(page).toHaveURL( `${PlatformURI}/workbench/sanity-ws/tracker/tracker%3Aproject%3ADefaultProject/components` diff --git a/tests/sanity/tests/tracker/filter.spec.ts b/tests/sanity/tests/tracker/filter.spec.ts index be0f1d8182..79b7e73014 100644 --- a/tests/sanity/tests/tracker/filter.spec.ts +++ b/tests/sanity/tests/tracker/filter.spec.ts @@ -235,6 +235,7 @@ test.describe('Tracker filters tests', () => { await leftSideMenuPage.buttonTracker.click() const issuesPage = new IssuesPage(page) + await issuesPage.linkSidebarAll.click() await issuesPage.modelSelectorAll.click() for (const status of DEFAULT_STATUSES) { diff --git a/tests/sanity/tests/tracker/issues.spec.ts b/tests/sanity/tests/tracker/issues.spec.ts index 39c39099f7..8b583c5904 100644 --- a/tests/sanity/tests/tracker/issues.spec.ts +++ b/tests/sanity/tests/tracker/issues.spec.ts @@ -317,6 +317,7 @@ test.describe('Tracker issue tests', () => { await trackerNavigationMenuPage.openIssuesForProject('Default') const issuesPage = new IssuesPage(page) + await issuesPage.searchIssueByName(commentIssue.title) await issuesPage.checkCommentsCount(commentIssue.title, '1') await issuesPage.openCommentPopupForIssueByName(commentIssue.title) @@ -327,6 +328,7 @@ test.describe('Tracker issue tests', () => { await issuesPage.modelSelectorAll.click() + await issuesPage.searchIssueByName(commentIssue.title) await issuesPage.checkCommentsCount(commentIssue.title, '2') }) }) diff --git a/tests/sanity/tests/tracker/public-link.spec.ts b/tests/sanity/tests/tracker/public-link.spec.ts index b15fbad953..661588f1d5 100644 --- a/tests/sanity/tests/tracker/public-link.spec.ts +++ b/tests/sanity/tests/tracker/public-link.spec.ts @@ -17,17 +17,22 @@ test.describe('Tracker public link issues tests', () => { await test.step('Get public link from popup', async () => { const newContext = await browser.newContext({ storageState: PlatformSetting }) const page = await newContext.newPage() - await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() + try { + await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() - const leftSideMenuPage = new LeftSideMenuPage(page) - await leftSideMenuPage.buttonTracker.click() - await prepareNewIssueWithOpenStep(page, publicLinkIssue) + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonTracker.click() + await prepareNewIssueWithOpenStep(page, publicLinkIssue) - const issuesDetailsPage = new IssuesDetailsPage(page) - await issuesDetailsPage.moreActionOnIssue('Public link') + const issuesDetailsPage = new IssuesDetailsPage(page) + await issuesDetailsPage.moreActionOnIssue('Public link') - const publicLinkPopup = new PublicLinkPopup(page) - link = await publicLinkPopup.getPublicLink() + const publicLinkPopup = new PublicLinkPopup(page) + link = await publicLinkPopup.getPublicLink() + } finally { + await page.close() + await newContext.close() + } }) await test.step('Check guest access to the issue', async () => { @@ -36,15 +41,20 @@ test.describe('Tracker public link issues tests', () => { await clearSession.clearPermissions() const clearPage = await clearSession.newPage() - await clearPage.goto(link) + try { + await clearPage.goto(link) - const clearIssuesDetailsPage = new IssuesDetailsPage(clearPage) - await clearIssuesDetailsPage.waitDetailsOpened(publicLinkIssue.title) - await clearIssuesDetailsPage.checkIssue({ - ...publicLinkIssue, - status: 'Backlog' - }) - expect(clearPage.url()).toContain('guest') + const clearIssuesDetailsPage = new IssuesDetailsPage(clearPage) + await clearIssuesDetailsPage.waitDetailsOpened(publicLinkIssue.title) + await clearIssuesDetailsPage.checkIssue({ + ...publicLinkIssue, + status: 'Backlog' + }) + expect(clearPage.url()).toContain('guest') + } finally { + await clearPage.close() + await clearSession.close() + } }) }) @@ -57,42 +67,52 @@ test.describe('Tracker public link issues tests', () => { const newContext = await browser.newContext({ storageState: PlatformSetting }) const page = await newContext.newPage() let link: string - await test.step('Get public link from popup', async () => { - await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() + try { + await test.step('Get public link from popup', async () => { + await (await page.goto(`${PlatformURI}/workbench/sanity-ws`))?.finished() - const leftSideMenuPage = new LeftSideMenuPage(page) - await leftSideMenuPage.buttonTracker.click() - await prepareNewIssueWithOpenStep(page, publicLinkIssue) + const leftSideMenuPage = new LeftSideMenuPage(page) + await leftSideMenuPage.buttonTracker.click() + await prepareNewIssueWithOpenStep(page, publicLinkIssue) - const issuesDetailsPage = new IssuesDetailsPage(page) - await issuesDetailsPage.moreActionOnIssue('Public link') + const issuesDetailsPage = new IssuesDetailsPage(page) + await issuesDetailsPage.moreActionOnIssue('Public link') - const publicLinkPopup = new PublicLinkPopup(page) - link = await publicLinkPopup.getPublicLink() - }) - - const clearSession = await browser.newContext() - const clearPage = await clearSession.newPage() - await test.step('Check guest access to the issue', async () => { - await clearPage.goto(link) - - const clearIssuesDetailsPage = new IssuesDetailsPage(clearPage) - await clearIssuesDetailsPage.waitDetailsOpened(publicLinkIssue.title) - await clearIssuesDetailsPage.checkIssue({ - ...publicLinkIssue, - status: 'Backlog' + const publicLinkPopup = new PublicLinkPopup(page) + link = await publicLinkPopup.getPublicLink() }) - expect(clearPage.url()).toContain('guest') - }) - await test.step('Revoke guest access to the issue', async () => { - const publicLinkPopup = new PublicLinkPopup(page) - await publicLinkPopup.revokePublicLink() - }) + const clearSession = await browser.newContext() + const clearPage = await clearSession.newPage() + try { + await test.step('Check guest access to the issue', async () => { + await clearPage.goto(link) - await test.step('Check guest access to the issue after the revoke', async () => { - await clearPage.goto(link) - await expect(clearPage.locator('div.antiPopup > h1')).toHaveText('Public link was revoked') - }) + const clearIssuesDetailsPage = new IssuesDetailsPage(clearPage) + await clearIssuesDetailsPage.waitDetailsOpened(publicLinkIssue.title) + await clearIssuesDetailsPage.checkIssue({ + ...publicLinkIssue, + status: 'Backlog' + }) + expect(clearPage.url()).toContain('guest') + }) + + await test.step('Revoke guest access to the issue', async () => { + const publicLinkPopup = new PublicLinkPopup(page) + await publicLinkPopup.revokePublicLink() + }) + + await test.step('Check guest access to the issue after the revoke', async () => { + await clearPage.goto(link) + await expect(clearPage.locator('div.antiPopup > h1')).toHaveText('Public link was revoked') + }) + } finally { + await clearPage.close() + await clearSession.close() + } + } finally { + await page.close() + await newContext.close() + } }) }) diff --git a/tests/sanity/tests/tracker/subissues.spec.ts b/tests/sanity/tests/tracker/subissues.spec.ts index f4897522ea..f74ca2d52f 100644 --- a/tests/sanity/tests/tracker/subissues.spec.ts +++ b/tests/sanity/tests/tracker/subissues.spec.ts @@ -23,7 +23,7 @@ test.describe('Tracker sub-issues tests', () => { }) test('create sub-issue', async ({ page }) => { - await navigate(page) + await page.click('[id="app-tracker\\:string\\:TrackerApplication"]') const props = { name: `issue-${generateId(5)}`, diff --git a/tests/sanity/tests/utils.ts b/tests/sanity/tests/utils.ts index 9bcd7141aa..0184966f23 100644 --- a/tests/sanity/tests/utils.ts +++ b/tests/sanity/tests/utils.ts @@ -1,4 +1,4 @@ -import { Browser, Locator, Page, expect } from '@playwright/test' +import { Browser, BrowserContext, Locator, Page, expect } from '@playwright/test' import { allure } from 'allure-playwright' export const PlatformURI = process.env.PLATFORM_URI as string @@ -62,9 +62,9 @@ export async function fillSearch (page: Page, search: string): Promise return searchBox } -export async function getSecondPage (browser: Browser): Promise { +export async function getSecondPage (browser: Browser): Promise<{ page: Page, context: BrowserContext }> { const userSecondContext = await browser.newContext({ storageState: PlatformSettingSecond }) - return await userSecondContext.newPage() + return { page: await userSecondContext.newPage(), context: userSecondContext } } export function expectToContainsOrdered (val: Locator, text: string[], timeout?: number): Promise { const origIssuesExp = new RegExp('.*' + text.join('.*') + '.*')