diff --git a/qms-tests/sanity/tests/documents/REQ-13.spec.ts b/qms-tests/sanity/tests/documents/REQ-03.spec.ts similarity index 98% rename from qms-tests/sanity/tests/documents/REQ-13.spec.ts rename to qms-tests/sanity/tests/documents/REQ-03.spec.ts index 30548ec554..3154e6d645 100644 --- a/qms-tests/sanity/tests/documents/REQ-13.spec.ts +++ b/qms-tests/sanity/tests/documents/REQ-03.spec.ts @@ -12,7 +12,7 @@ import { allure } from 'allure-playwright' import { DocumentsPage } from '../model/documents/documents-page' import { DocumentDetails, DocumentRights, DocumentStatus, NewDocument } from '../model/types' import { DocumentContentPage } from '../model/documents/document-content-page' -import { prepareDocumentStep } from '../documents/common-documents-steps' +import { prepareDocumentStep } from './common-documents-steps' import { DocumentHistoryPage } from '../model/documents/document-history-page' diff --git a/qms-tests/sanity/tests/documents/REQ-04.spec.ts b/qms-tests/sanity/tests/documents/REQ-04.spec.ts new file mode 100644 index 0000000000..d5ec06f25c --- /dev/null +++ b/qms-tests/sanity/tests/documents/REQ-04.spec.ts @@ -0,0 +1,169 @@ +import { test, expect } from '@playwright/test' +import { + attachScreenshot, + DocumentURI, + generateId, + getSecondPage, + HomepageURI, + PlatformSetting, + PlatformURI +} from '../utils' +import { allure } from 'allure-playwright' +import { DocumentsPage } from '../model/documents/documents-page' +import { Content, DocumentDetails, DocumentRights, DocumentStatus, NewDocument } from '../model/types' +import { DocumentContentPage } from '../model/documents/document-content-page' +import { prepareDocumentStep } from './common-documents-steps' + +import { faker } from '@faker-js/faker' + +test.use({ + storageState: PlatformSetting +}) + +test.describe('ISO 13485, 4.2.4 Control of documents', () => { + test.beforeEach(async ({ page }) => { + await (await page.goto(`${PlatformURI}/${HomepageURI}`))?.finished() + }) + + test('TESTS-399. Tool does not influence content: Entered text in a document does not change over time', async ({ + page, + browser + }) => { + await allure.description( + 'Requirement\nUsers need to make a resolve all comments and done documents for the Effective status' + ) + await allure.tms('TESTS-399', 'https://tracex.hc.engineering/workbench/platform/tracker/TESTS-399') + + const userSecondPage = await getSecondPage(browser) + const completeDocument: NewDocument = { + template: 'HR (HR)', + title: `Complete document-${generateId()}`, + description: `Complete document description-${generateId()}` + } + const reviewer = 'Dirak Kainin' + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + const newContent: Content = { + sectionTitle: `Overview-${generateId()}`, + content: faker.lorem.paragraphs(10) + } + + await prepareDocumentStep(page, completeDocument) + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.updateSectionTitle('1', newContent.sectionTitle) + await documentContentPage.addContentToTheSection(newContent) + + await test.step('2. Send for Approval', async () => { + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm([reviewer]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_APPROVAL) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_APPROVAL, + version: 'v0.1' + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + + await test.step('3. Approve document', async () => { + const documentsPageSecond = new DocumentsPage(userSecondPage) + await (await userSecondPage.goto(`${PlatformURI}/${DocumentURI}`))?.finished() + await documentsPageSecond.openDocument(completeDocument.title) + + const documentContentPageSecond = new DocumentContentPage(userSecondPage) + await documentContentPageSecond.confirmApproval() + + await documentContentPageSecond.checkDocumentStatus(DocumentStatus.EFFECTIVE) + await documentContentPageSecond.checkDocument({ + ...documentDetails, + status: DocumentStatus.EFFECTIVE, + version: 'v0.1' + }) + await documentContentPageSecond.checkCurrentRights(DocumentRights.VIEWING) + + await attachScreenshot('TESTS-399_approve_document.png', page) + }) + + await test.step('4. Check the content of the draft matches the approved version', async () => { + const actualText = await page.locator('.tiptap').innerText() + const expectedText = newContent.content.trim().replace(/\s+/g, ' ') + const cleanedActualText = actualText.trim().replace(/\s+/g, ' ') + + expect(cleanedActualText).toContain(expectedText) + await attachScreenshot('TESTS-399-check_content.png', page) + }) + }) + + test.skip('TESTS-271. Generate a PDF from an Effective doc', async ({ page, browser }) => { + await allure.description( + 'Requirement\nUsers need to make a resolve all comments and done documents for the Effective status' + ) + await allure.tms('TESTS-271', 'https://tracex.hc.engineering/workbench/platform/tracker/TESTS-271') + + const userSecondPage = await getSecondPage(browser) + const completeDocument: NewDocument = { + template: 'HR (HR)', + title: `Complete document-${generateId()}`, + description: `Complete document description-${generateId()}` + } + const reviewer = 'Dirak Kainin' + const documentDetails: DocumentDetails = { + type: 'HR', + category: 'Human Resources', + version: 'v0.1', + status: DocumentStatus.DRAFT, + owner: 'Appleseed John', + author: 'Appleseed John' + } + const newContent: Content = { + sectionTitle: `Overview-${generateId()}`, + content: faker.lorem.paragraphs(10) + } + + await prepareDocumentStep(page, completeDocument) + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.updateSectionTitle('1', newContent.sectionTitle) + await documentContentPage.addContentToTheSection(newContent) + + await test.step('2. Send for Approval', async () => { + await documentContentPage.buttonSendForApproval.click() + await documentContentPage.fillSelectApproversForm([reviewer]) + await documentContentPage.checkDocumentStatus(DocumentStatus.IN_APPROVAL) + await documentContentPage.checkDocument({ + ...documentDetails, + status: DocumentStatus.IN_APPROVAL, + version: 'v0.1' + }) + await documentContentPage.checkCurrentRights(DocumentRights.VIEWING) + }) + + await test.step('3. Approve document', async () => { + const documentsPageSecond = new DocumentsPage(userSecondPage) + await (await userSecondPage.goto(`${PlatformURI}/${DocumentURI}`))?.finished() + await documentsPageSecond.openDocument(completeDocument.title) + + const documentContentPageSecond = new DocumentContentPage(userSecondPage) + await documentContentPageSecond.confirmApproval() + + await documentContentPageSecond.checkDocumentStatus(DocumentStatus.EFFECTIVE) + await documentContentPageSecond.checkDocument({ + ...documentDetails, + status: DocumentStatus.EFFECTIVE, + version: 'v0.1' + }) + await documentContentPageSecond.checkCurrentRights(DocumentRights.VIEWING) + + await attachScreenshot('TESTS-271_approve_document.png', page) + }) + + await test.step('4. Download PDF', async () => { + await attachScreenshot('TESTS-271-check_content.png', page) + }) + }) +}) diff --git a/qms-tests/sanity/tests/documents/REQ-05.spec.ts b/qms-tests/sanity/tests/documents/REQ-05.spec.ts new file mode 100644 index 0000000000..f5a7c19572 --- /dev/null +++ b/qms-tests/sanity/tests/documents/REQ-05.spec.ts @@ -0,0 +1,119 @@ +import { test } from '@playwright/test' +import { attachScreenshot, generateId, HomepageURI, PlatformSetting, PlatformURI } from '../utils' +import { allure } from 'allure-playwright' +import { DocumentContentPage } from '../model/documents/document-content-page' +import { LeftSideMenuPage } from '../model/left-side-menu-page' + +import { faker } from '@faker-js/faker' +import { createTemplateStep } from './common-documents-steps' + +test.use({ + storageState: PlatformSetting +}) + +test.describe('ISO 13485, 4.2.4 Control of documents', () => { + test.beforeEach(async ({ page }) => { + await (await page.goto(`${PlatformURI}/${HomepageURI}`))?.finished() + }) + + test('TESTS-298. Create a new Category from top right corner', async ({ page }) => { + await allure.description('Requirement\nUsers need to create a new category') + await allure.tms('TESTS-298', 'https://tracex.hc.engineering/workbench/platform/tracker/TESTS-298') + + const title = faker.word.words(2) + const description = faker.lorem.sentence(1) + const code = faker.word.words(2) + const leftSideMenuPage = new LeftSideMenuPage(page) + + await leftSideMenuPage.clickButtonOnTheLeft('Documents') + await test.step('2. Create a new category', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.selectControlDocumentSubcategory('Categories') + await documentContentPage.clickOnAddCategoryButton() + await documentContentPage.fillCategoryForm(title, description, code) + await documentContentPage.checkIfCategoryIsCreated(title, code) + }) + + await attachScreenshot('TESTS-298_category_created.png', page) + }) + + test('TESTS-381. As a workspace user, I can create a new space and label it External Doc', async ({ page }) => { + await allure.description('Requirement\nUsers need to create a new space') + await allure.tms('TESTS-381', 'https://tracex.hc.engineering/workbench/platform/tracker/TESTS-381') + const leftSideMenuPage = new LeftSideMenuPage(page) + const folderName = generateId(5) + + await leftSideMenuPage.clickButtonOnTheLeft('Documents') + await test.step('2. Create a new document space', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.clickAddFolderButton() + await documentContentPage.fillDocumentSpaceForm(folderName) + await documentContentPage.checkSpaceFormIsCreated(folderName) + await documentContentPage.clickLeaveFolder(folderName) + }) + + await attachScreenshot('TESTS-381_document_space_created.png', page) + }) + + test('TESTS-382. Create new a new Effective Template with category External', async ({ page }) => { + await allure.description('Requirement\nUsers need to create a new template') + await allure.tms('TESTS-382', 'https://tracex.hc.engineering/workbench/platform/tracker/TESTS-382') + const leftSideMenuPage = new LeftSideMenuPage(page) + const category = faker.word.words(2) + const description = faker.lorem.sentence(1) + const code = faker.word.words(2) + const title = faker.word.words(2) + + await leftSideMenuPage.clickButtonOnTheLeft('Documents') + await test.step('2. Create a new category', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.selectControlDocumentSubcategory('Categories') + await documentContentPage.clickOnAddCategoryButton() + await documentContentPage.fillCategoryForm(category, description, code) + await documentContentPage.checkIfCategoryIsCreated(category, code) + }) + await test.step('3. Create a new template', async () => { + const documentContentPage = new DocumentContentPage(page) + + await documentContentPage.clickNewDocumentArrow() + await documentContentPage.clickNewTemplate() + await createTemplateStep(page, title, description, category) + }) + + await attachScreenshot('TESTS-382_Template_created.png', page) + }) + + test('TESTS-383. authorized User can search a doc per category "External"', async ({ page }) => { + await allure.description('Requirement\nUsers need to create a new category and space') + await allure.tms('TESTS-383', 'https://tracex.hc.engineering/workbench/platform/tracker/TESTS-383') + + const title = faker.word.words(2) + const description = faker.lorem.sentence(1) + const code = faker.word.words(2) + const leftSideMenuPage = new LeftSideMenuPage(page) + const category = faker.word.words(2) + + await leftSideMenuPage.clickButtonOnTheLeft('Documents') + await test.step('2. Create a new category', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.selectControlDocumentSubcategory('Categories') + await documentContentPage.clickOnAddCategoryButton() + await documentContentPage.fillCategoryForm(category, description, code) + await documentContentPage.checkIfCategoryIsCreated(category, code) + }) + await test.step('3. Create a new template', async () => { + const documentContentPage = new DocumentContentPage(page) + + await documentContentPage.clickNewDocumentArrow() + await documentContentPage.clickNewTemplate() + await createTemplateStep(page, title, description, category) + }) + await test.step('4. Check if templates exists in template category', async () => { + const documentContentPage = new DocumentContentPage(page) + await documentContentPage.selectControlDocumentSubcategory('Templates') + await documentContentPage.chooseFilter(code) + await documentContentPage.checkIfFilterIsApplied(title) + }) + await attachScreenshot('TESTS-383_Template_created.png', page) + }) +}) diff --git a/qms-tests/sanity/tests/documents/common-documents-steps.ts b/qms-tests/sanity/tests/documents/common-documents-steps.ts index 921baa6505..9d234b29b5 100644 --- a/qms-tests/sanity/tests/documents/common-documents-steps.ts +++ b/qms-tests/sanity/tests/documents/common-documents-steps.ts @@ -18,6 +18,19 @@ export async function prepareDocumentStep (page: Page, document: NewDocument, st }) } +export async function createTemplateStep ( + page: Page, + title: string, + description: string, + category: string +): Promise { + await test.step('2. Create a new template', async () => { + const documentsPage = new DocumentsPage(page) + + await documentsPage.createTemplate(title, description, category) + }) +} + export async function prepareCategoryStep (page: Page, newCategory: NewCategory): Promise { await test.step('1. Create a new category', async () => { const leftSideMenuPage = new LeftSideMenuPage(page) diff --git a/qms-tests/sanity/tests/model/documents/document-content-page.ts b/qms-tests/sanity/tests/model/documents/document-content-page.ts index a026012d27..1f447b2a5a 100644 --- a/qms-tests/sanity/tests/model/documents/document-content-page.ts +++ b/qms-tests/sanity/tests/model/documents/document-content-page.ts @@ -58,6 +58,20 @@ export class DocumentContentPage extends DocumentCommonPage { readonly buttonHistoryTab: Locator readonly documentHeader: Locator readonly leaveFolder: Locator + readonly textContainer: Locator + readonly myDocument: Locator + readonly library: Locator + readonly templates: Locator + readonly categories: Locator + readonly addCategoryButton: Locator + readonly categoryTitle: Locator + readonly description: Locator + readonly categoryCode: Locator + readonly generalDocumentation: Locator + readonly newDocumentArrow: Locator + readonly newTemplate: Locator + readonly filter: Locator + readonly filterCategory: Locator constructor (page: Page) { super(page) @@ -126,6 +140,20 @@ export class DocumentContentPage extends DocumentCommonPage { this.buttonHistoryTab = page.getByText('History') this.documentHeader = page.getByRole('button', { name: 'Complete document' }) this.leaveFolder = page.getByRole('button', { name: 'Leave' }) + this.textContainer = page.locator('.tiptap') + this.myDocument = page.getByRole('button', { name: 'Categories' }) + this.library = page.getByRole('button', { name: 'Library' }) + this.templates = page.getByRole('button', { name: 'Templates' }) + this.categories = page.getByRole('button', { name: 'Categories' }) + this.addCategoryButton = page.getByRole('button', { name: 'Category', exact: true }) + this.categoryTitle = page.getByPlaceholder('Title') + this.description = page.getByRole('paragraph') + this.categoryCode = page.getByPlaceholder('Code') + this.generalDocumentation = page.getByRole('button', { name: 'General documentation' }) + this.newDocumentArrow = page.locator('.w-full > button:nth-child(2)') + this.newTemplate = page.getByRole('button', { name: 'New template', exact: true }) + this.filter = page.getByRole('button', { name: 'Filter' }) + this.filterCategory = page.locator('span').filter({ hasText: /^Category$/ }) } async checkDocumentTitle (title: string): Promise { @@ -144,6 +172,62 @@ export class DocumentContentPage extends DocumentCommonPage { .fill(title) } + async clickOnAddCategoryButton (): Promise { + await this.addCategoryButton.click() + } + + async clickNewDocumentArrow (): Promise { + await this.newDocumentArrow.click() + } + + async clickNewTemplate (): Promise { + await this.newTemplate.click() + } + + async selectControlDocumentSubcategory ( + buttonName: 'My Document' | 'Library' | 'Templates' | 'Categories' | 'General documentation' + ): Promise { + switch (buttonName) { + case 'My Document': + await this.myDocument.click() + break + case 'Library': + await this.library.click() + break + case 'Templates': + await this.templates.click() + break + case 'Categories': + await this.categories.click() + break + case 'General documentation': + await this.generalDocumentation.click() + break + default: + throw new Error('Unknown button') + } + } + + async fillCategoryForm (categoryTitle: string, description: string, categoryCode: string): Promise { + await this.categoryTitle.fill(categoryTitle) + await this.description.fill(description) + await this.categoryCode.fill(categoryCode) + await this.createButton.click() + } + + async checkIfCategoryIsCreated (categoryTitle: string, categoryCode: string): Promise { + await expect(this.page.getByText(categoryTitle)).toBeVisible() + await expect(this.page.getByRole('link', { name: categoryCode })).toBeVisible() + } + + async checkIfTextExists (text: string): Promise { + await expect(this.textContainer).toContainText(text) + } + + async hoverOverGeneralDocumentation (): Promise { + await this.generalDocumentation.hover() + } + async addReasonAndImpactToTheDocument (description: string, reason: string): Promise { await this.page.getByText('Reason & Impact').click() await this.page.getByPlaceholder('Describe what was changed...').fill(description) @@ -199,15 +283,34 @@ export class DocumentContentPage extends DocumentCommonPage { await this.addSpaceButton.click() } + async chooseFilter (category: string): Promise { + await this.filter.click() + await this.filterCategory.hover() + await this.page.getByRole('button', { name: 'Category', exact: true }).click() + await this.page.getByText(category).click({ force: true }) + await this.page.keyboard.press('Escape') + } + + async checkIfFilterIsApplied (code: string): Promise { + await expect(this.page.getByText(code, { exact: true })).toBeVisible() + } + async fillDocumentSpaceForm (spaceName: string): Promise { await this.inputSpaceName.fill(spaceName) await this.roleSelector.nth(2).click() await this.selectRoleMember.nth(2).click() await this.page.keyboard.press('Escape') + await this.selectRoleMember.nth(1).click() + await this.page.getByRole('button', { name: 'DK Dirak Kainin' }).click() + await this.page.keyboard.press('Escape') await this.page.waitForTimeout(1000) await this.createButton.click() } + async checkSpaceFormIsCreated (spaceName: string): Promise { + await expect(this.page.getByRole('button', { name: spaceName })).toBeVisible() + } + async createNewDocumentInsideFolder (folderName: string): Promise { await this.page.getByRole('button', { name: folderName }).hover() await this.page.getByRole('button', { name: folderName }).getByRole('button').click() diff --git a/qms-tests/sanity/tests/model/documents/documents-page.ts b/qms-tests/sanity/tests/model/documents/documents-page.ts index d5930d8a40..a15c74f79e 100644 --- a/qms-tests/sanity/tests/model/documents/documents-page.ts +++ b/qms-tests/sanity/tests/model/documents/documents-page.ts @@ -10,6 +10,11 @@ export class DocumentsPage extends CalendarPage { readonly inputNewDocumentTitle: Locator readonly inputNewDocumentDescription: Locator readonly inputNewDocumentCreateDaft: Locator + readonly category: Locator + readonly search: Locator + readonly nextStep: Locator + readonly addMember: Locator + readonly newMember: Locator constructor (page: Page) { super(page) @@ -23,6 +28,13 @@ export class DocumentsPage extends CalendarPage { this.inputNewDocumentTitle = page.locator('div[id="doc-title"] input') this.inputNewDocumentDescription = page.locator('div[id="doc-description"] input') this.inputNewDocumentCreateDaft = page.locator('div.footer div.footerButtons button[type="button"]') + this.category = page.locator( + "xpath=//button[@class='antiButton no-border small jf-center sh-no-shape bs-solid gap-medium iconR']" + ) + this.search = page.getByPlaceholder('Search...') + this.nextStep = page.getByRole('button', { name: 'Next step' }) + this.addMember = page.getByText('Add member') + this.newMember = page.getByRole('button', { name: 'AJ Appleseed John' }) } async createDocument (data: NewDocument, startSecondStep: boolean = false): Promise { @@ -54,6 +66,20 @@ export class DocumentsPage extends CalendarPage { await this.inputNewDocumentCreateDaft.click() } + async createTemplate (title: string, description: string, category: string): Promise { + await this.buttonPopupNextStep.click() + await this.inputNewDocumentTitle.fill(title) + await this.inputNewDocumentDescription.fill(description) + await this.category.click() + await this.search.fill(category) + await this.page.getByRole('button', { name: category }).click() + await this.nextStep.click() + await this.addMember.nth(2).click() + await this.newMember.click() + await this.page.keyboard.press('Escape') + await this.inputNewDocumentCreateDaft.click() + } + async openDocument (name: string): Promise { await this.page.locator('button.hulyNavItem-container > span[class*="label"]', { hasText: name }).click() } diff --git a/qms-tests/sanity/tests/model/left-side-menu-page.ts b/qms-tests/sanity/tests/model/left-side-menu-page.ts index 1f81d7e83a..18d80bcc27 100644 --- a/qms-tests/sanity/tests/model/left-side-menu-page.ts +++ b/qms-tests/sanity/tests/model/left-side-menu-page.ts @@ -12,4 +12,20 @@ export class LeftSideMenuPage { this.buttonTeam = page.locator('button[id$="Team"]') this.buttonDocuments = page.locator('[id$="app-documents\\:string\\:DocumentApplication"]') } + + async clickButtonOnTheLeft (buttonName: 'Planning' | 'Team' | 'Documents'): Promise { + switch (buttonName) { + case 'Planning': + await this.buttonPlanning.click() + break + case 'Team': + await this.buttonTeam.click() + break + case 'Documents': + await this.buttonDocuments.click() + break + default: + throw new Error('Unknown button') + } + } } diff --git a/tests/sanity/tests/playwright.config.ts b/tests/sanity/tests/playwright.config.ts index 1f977da65e..1e1cb379c1 100644 --- a/tests/sanity/tests/playwright.config.ts +++ b/tests/sanity/tests/playwright.config.ts @@ -28,7 +28,8 @@ const config: PlaywrightTestConfig = { contextOptions: { reducedMotion: 'reduce' } - } + }, + fullyParallel: false } ], retries: 2,