diff --git a/tests/e2e/pageObjects/workbench-page.ts b/tests/e2e/pageObjects/workbench-page.ts index a1b841037c..36c14b8c05 100644 --- a/tests/e2e/pageObjects/workbench-page.ts +++ b/tests/e2e/pageObjects/workbench-page.ts @@ -33,8 +33,8 @@ export class WorkbenchPage { customTutorials = Selector('[data-testid=accordion-button-custom-tutorials]'); tutorialOpenUploadButton = Selector('[data-testid=open-upload-tutorial-btn]'); tutorialLinkField = Selector('[data-testid=tutorial-link-field]'); - tutorialLatestDeleteIcon = Selector('[data-testid^=delete-tutorial-icon-]').nth(0); - tutorialDeleteButton = Selector('[data-testid^=delete-tutorial-]').withText('Delete'); + tutorialLatestDeleteIcon = Selector('[data-testid^=delete-tutorial-icon-]').nth(0); + tutorialDeleteButton = Selector('[data-testid^=delete-tutorial-]').withText('Delete'); tutorialNameField = Selector('[data-testid=tutorial-name-field]'); tutorialSubmitButton = Selector('[data-testid=submit-upload-tutorial-btn]'); tutorialImport = Selector('[data-testid=import-tutorial]'); @@ -233,6 +233,7 @@ export class WorkbenchPage { const actualCommandResult = await this.queryCardContainer.nth(childNum).find(this.cssQueryTextResult).textContent; await t.expect(actualCommandResult).contains(result, 'Actual command result is not equal to executed'); } + /** * Get selector with tutorial name * @param tutorialName name of the uploaded tutorial @@ -240,6 +241,7 @@ export class WorkbenchPage { async getAccordionButtonWithName(tutorialName: string): Promise { return Selector(`[data-testid=accordion-button-${tutorialName}]`); } + /** * Get internal tutorial link with .md name * @param internalLink name of the .md file @@ -247,6 +249,7 @@ export class WorkbenchPage { async getInternalLinkWithManifest(internalLink: string): Promise { return Selector(`[data-testid="internal-link-${internalLink}.md"]`); } + /** * Get internal tutorial link without .md name * @param internalLink name of the label @@ -254,6 +257,7 @@ export class WorkbenchPage { async getInternalLinkWithoutManifest(internalLink: string): Promise { return Selector(`[data-testid="internal-link-${internalLink}"]`); } + /** * Find tutorial selector by name * @param name A tutorial name @@ -261,4 +265,27 @@ export class WorkbenchPage { async getTutorialByName(name: string): Promise { return Selector('div').withText(name); } + + /** + * Find image in tutorial by alt text + * @param alt Image alt text + */ + async getTutorialImageByAlt(alt: string): Promise { + return Selector('img').withAttribute('alt', alt); + } + + /** + * Wait until image rendered + * @param selector Image selector + */ + async waitUntilImageRendered(selector: Selector): Promise { + const searchTimeout = 5 * 1000; // 5 sec maximum wait + const startTime = Date.now(); + let imageHeight = await selector.getStyleProperty('height'); + + do { + imageHeight = await selector.getStyleProperty('height'); + } + while ((imageHeight == '0px') && Date.now() - startTime < searchTimeout); + } } diff --git a/tests/e2e/test-data/upload-tutorials/testTutorials.zip b/tests/e2e/test-data/upload-tutorials/testTutorials.zip index 60192fedb8..365ef7c425 100644 Binary files a/tests/e2e/test-data/upload-tutorials/testTutorials.zip and b/tests/e2e/test-data/upload-tutorials/testTutorials.zip differ diff --git a/tests/e2e/tests/regression/workbench/import-tutorials.e2e.ts b/tests/e2e/tests/regression/workbench/import-tutorials.e2e.ts index 89a171c057..13e5c04ee6 100644 --- a/tests/e2e/tests/regression/workbench/import-tutorials.e2e.ts +++ b/tests/e2e/tests/regression/workbench/import-tutorials.e2e.ts @@ -19,30 +19,37 @@ let internalLinkName1 = 'probably-1'; let internalLinkName2 = 'vector-2'; fixture `Upload custom tutorials` - .meta({type: 'regression', rte: rte.standalone}) + .meta({ type: 'regression', rte: rte.standalone }) .page(commonUrl) .beforeEach(async t => { await acceptLicenseTermsAndAddDatabaseApi(ossStandaloneConfig, ossStandaloneConfig.databaseName); await t.click(myRedisDatabasePage.workbenchButton); }) - .afterEach(async() => { + .afterEach(async () => { await deleteStandaloneDatabaseApi(ossStandaloneConfig); }); -// https://redislabs.atlassian.net/browse/RI-4186, https://redislabs.atlassian.net/browse/RI-4198, https://redislabs.atlassian.net/browse/RI-4302 +/* https://redislabs.atlassian.net/browse/RI-4186, https://redislabs.atlassian.net/browse/RI-4198, +https://redislabs.atlassian.net/browse/RI-4302, https://redislabs.atlassian.net/browse/RI-4318 +*/ test('Verify that user can upload tutorial with local zip file without manifest.json', async t => { // Verify that user can upload custom tutorials on docker version + const imageExternalPath = 'RedisInsight screen external'; + const imageRelativePath = 'RedisInsight screen relative'; folder1 = 'folder-1'; folder2 = 'folder-2'; internalLinkName1 = 'probably-1'; internalLinkName2 = 'vector-2'; + // Verify that user can see the “MY TUTORIALS” section in the Enablement area. await t.expect(workbenchPage.customTutorials.visible).ok('custom tutorials sections is not visible'); await t.click(workbenchPage.tutorialOpenUploadButton); await t.expect(workbenchPage.tutorialSubmitButton.hasAttribute('disabled')).ok('submit button is not disabled'); + // Verify that User can request to add a new custom Tutorial by uploading a .zip archive from a local folder await t.setFilesToUpload(workbenchPage.tutorialImport, [filePath]); await t.click(workbenchPage.tutorialSubmitButton); await t.expect(workbenchPage.tutorialAccordionButton.withText(tutorialName).visible).ok(`${tutorialName} tutorial is not uploaded`); + // Verify that when user upload a .zip archive without a .json manifest, all markdown files are inserted at the same hierarchy level await t.click(workbenchPage.tutorialAccordionButton.withText(tutorialName)); await t.expect((await workbenchPage.getAccordionButtonWithName(folder1)).visible).ok(`${folder1} is not visible`); @@ -52,16 +59,29 @@ test('Verify that user can upload tutorial with local zip file without manifest. .ok(`${internalLinkName1} is not visible`); await t.click(await workbenchPage.getAccordionButtonWithName(folder2)); await t.expect((await workbenchPage.getInternalLinkWithManifest(internalLinkName2)).visible) - .ok(`${internalLinkName1} is not visible`); + .ok(`${internalLinkName2} is not visible`); await t.expect(workbenchPage.scrolledEnablementArea.exists).notOk('enablement area is visible before clicked'); await t.click((await workbenchPage.getInternalLinkWithManifest(internalLinkName1))); await t.expect(workbenchPage.scrolledEnablementArea.visible).ok('enablement area is not visible after clicked'); + + // Verify that user can see image in custom tutorials by providing absolute external path in md file + const imageExternal = await workbenchPage.getTutorialImageByAlt(imageExternalPath); + await workbenchPage.waitUntilImageRendered(imageExternal); + const imageExternalHeight = await imageExternal.getStyleProperty('height'); + await t.expect(parseInt(imageExternalHeight.replace(/[^\d]/g, ''))).gte(150); + + // Verify that user can see image in custom tutorials by providing relative path in md file + const imageRelative = await workbenchPage.getTutorialImageByAlt(imageRelativePath); + await workbenchPage.waitUntilImageRendered(imageRelative); + const imageRelativeHeight = await imageRelative.getStyleProperty('height'); + await t.expect(parseInt(imageRelativeHeight.replace(/[^\d]/g, ''))).gte(150); + + // Verify that when User delete the tutorial, then User can see this tutorial and relevant markdown files are deleted from: the Enablement area in Workbench await t.click(workbenchPage.closeEnablementPage); await t.click(workbenchPage.tutorialLatestDeleteIcon); await t.expect(workbenchPage.tutorialDeleteButton.visible).ok('Delete popup is not visible'); await t.click(workbenchPage.tutorialDeleteButton); await t.expect(workbenchPage.tutorialDeleteButton.exists).notOk('Delete popup is still visible'); - // Verify that when User delete the tutorial, then User can see this tutorial and relevant markdown files are deleted from: the Enablement area in Workbench await t.expect((workbenchPage.tutorialAccordionButton.withText(tutorialName).exists)) .notOk(`${tutorialName} tutorial is not uploaded`); });