Repository: owncloud/web-extensions
Pull request #186: test: add e2e tests to check cast file-action
Author: login: saw-jan
Created: 2025-05-29T07:14:56Z | Merged: 2025-05-30T10:26:23Z

**JIRA:** [#OCISDEV-68](https://kiteworks.atlassian.net/browse/OCISDEV-68)
---

Added webUI e2e test to check the "Cast" file-action which is added by `web-app-cast` extension
Currently, the test is expected to fail as the extension doesn't work with firefox, webkit and headless chromium
See https://github.com/owncloud/web-extensions/issues/187

Part of https://github.com/owncloud/web-extensions/issues/118


Diff:
diff --git a/.drone.star b/.drone.star
index 3e732ca2..9b18b807 100644
--- a/.drone.star
+++ b/.drone.star
@@ -23,6 +23,7 @@ E2E_COVERED_APPS = [
     "progress-bars",
     "json-viewer",
     "external-sites",
+    "cast",
 ]
 
 OCIS_URL = "https://ocis:9200"
diff --git a/dev/docker/csp.yaml b/dev/docker/csp.yaml
index 24a443ab..09ad9992 100644
--- a/dev/docker/csp.yaml
+++ b/dev/docker/csp.yaml
@@ -32,6 +32,7 @@ directives:
   script-src:
     - "'self'"
     - "'unsafe-inline'"
+    - 'https://www.gstatic.com/'
   style-src:
     - "'self'"
     - "'unsafe-inline'"
diff --git a/packages/web-app-cast/tests/e2e/castFileAction.spec.ts b/packages/web-app-cast/tests/e2e/castFileAction.spec.ts
new file mode 100644
index 00000000..4660f63a
--- /dev/null
+++ b/packages/web-app-cast/tests/e2e/castFileAction.spec.ts
@@ -0,0 +1,29 @@
+import { test, Page, expect } from '@playwright/test'
+import { FilesAppBar } from '../../../../support/pages/filesAppBarActions'
+import { loginAsUser, logout } from '../../../../support/helpers/authHelper'
+import { FilesPage } from '../../../../support/pages/filesPage'
+
+let adminPage: Page
+
+test.beforeEach(async ({ browser }) => {
+  const admin = await loginAsUser(browser, 'admin', 'admin')
+  adminPage = admin.page
+})
+
+test.afterEach(async () => {
+  const filesPage = new FilesPage(adminPage)
+  await filesPage.deleteAllFromPersonal()
+  await logout(adminPage)
+})
+
+// Issue with firefox, webkit and headless browsers
+// https://github.com/owncloud/web-extensions/issues/187
+// TODO: remove 'fail' when the issue is fixed
+test.fail('check cast file-action', async () => {
+  const uploadFile = new FilesAppBar(adminPage)
+  await uploadFile.uploadFile('logo.jpeg')
+
+  const filePage = new FilesPage(adminPage)
+  await filePage.openFileContextMenu('logo.jpeg')
+  await expect(filePage.castFileActionBtn).toBeVisible()
+})
diff --git a/packages/web-app-cast/vite.config.ts b/packages/web-app-cast/vite.config.ts
index 5a7fbe63..8131e390 100644
--- a/packages/web-app-cast/vite.config.ts
+++ b/packages/web-app-cast/vite.config.ts
@@ -11,5 +11,8 @@ export default defineConfig({
         entryFileNames: 'cast.js'
       }
     }
+  },
+  test: {
+    exclude: ['**/e2e/**']
   }
 })
diff --git a/packages/web-app-json-viewer/tests/e2e/jsonViewer.spec.ts b/packages/web-app-json-viewer/tests/e2e/jsonViewer.spec.ts
index 863d59b0..54dfd541 100644
--- a/packages/web-app-json-viewer/tests/e2e/jsonViewer.spec.ts
+++ b/packages/web-app-json-viewer/tests/e2e/jsonViewer.spec.ts
@@ -21,6 +21,6 @@ test('open json file', async () => {
   await uploadFile.uploadFile('jsonFile.json')
 
   const filePage = new FilesPage(adminPage)
-  await filePage.openJsonFile()
+  await filePage.openJsonFile('jsonFile.json')
   await expect(filePage.jsonViewerSelector).toBeVisible()
 })
diff --git a/playwright.config.ts b/playwright.config.ts
index e4388e91..956697e3 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -29,6 +29,7 @@ export default defineConfig({
 
     /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
     trace: 'on-first-retry',
+    headless: true
   },
 
   /* Configure projects for major browsers */
@@ -108,7 +109,21 @@ export default defineConfig({
       testDir: './packages/web-app-external-sites/tests/e2e',
       use: { ...devices['Desktop Safari'], browserName: 'webkit', ignoreHTTPSErrors: true },
     },
-
+    {
+      name: 'cast-chromium',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Chrome'], browserName: 'chromium', ignoreHTTPSErrors: true },
+    },
+    {
+      name: 'cast-firefox',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Firefox'], browserName: 'firefox', ignoreHTTPSErrors: true },
+    },
+    {
+      name: 'cast-webkit',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Safari'], browserName: 'webkit', ignoreHTTPSErrors: true },
+    }
     /* Test against mobile viewports. */
     // {
     //   name: 'Mobile Chrome',
diff --git a/support/filesForUpload/logo.jpeg b/support/filesForUpload/logo.jpeg
new file mode 100644
index 00000000..1d140b76
Binary files /dev/null and b/support/filesForUpload/logo.jpeg differ
diff --git a/support/pages/filesAppBarActions.ts b/support/pages/filesAppBarActions.ts
index faaf9f0f..8e2e325e 100644
--- a/support/pages/filesAppBarActions.ts
+++ b/support/pages/filesAppBarActions.ts
@@ -1,23 +1,28 @@
-import { Locator, Page } from '@playwright/test'
+import { Locator, Page, expect } from '@playwright/test'
 import path from 'path'
 
 export class FilesAppBar {
   readonly page: Page
   readonly uploadBtn: Locator
   readonly uploadFileBtn: Locator
+  readonly closeUploadDialogBtn: Locator
+  readonly newResourceContextMenu: Locator
+  readonly uploadResourceContextMenu: Locator
 
   constructor(page: Page) {
     this.page = page
     this.uploadBtn = this.page.locator('#upload-menu-btn')
     this.uploadFileBtn = this.page.locator('#files-file-upload-input')
+    this.closeUploadDialogBtn = this.page.locator('#close-upload-info-btn')
+    this.newResourceContextMenu = this.page.locator('#upload-menu-drop')
+    this.uploadResourceContextMenu = this.page.locator('#new-file-menu-drop')
   }
 
   async uploadFile(file: string) {
     await this.uploadBtn.click()
     const realPath = path.join('./support/filesForUpload', file)
-  
-    let uploadAction: Promise<void> = this.uploadFileBtn.setInputFiles(path.resolve(realPath))
-   
+    const uploadAction: Promise<void> = this.uploadFileBtn.setInputFiles(path.resolve(realPath))
+
     await Promise.all([
       this.page.waitForResponse(
         (resp) =>
@@ -26,7 +31,8 @@ export class FilesAppBar {
       ),
       uploadAction
     ])
-    // close upload menu. Sometimes it hangs
-    await this.page.keyboard.press('Escape')
+    await this.closeUploadDialogBtn.click()
+    await expect(this.newResourceContextMenu).not.toBeVisible()
+    await expect(this.uploadResourceContextMenu).not.toBeVisible()
   }
-}
\ No newline at end of file
+}
diff --git a/support/pages/filesPage.ts b/support/pages/filesPage.ts
index 21373d14..ca2e2b25 100644
--- a/support/pages/filesPage.ts
+++ b/support/pages/filesPage.ts
@@ -9,7 +9,7 @@ export class FilesPage {
   readonly jsonViewerBtn: Locator
   readonly resourceActionDropDownBtn: Locator
   readonly jsonViewerSelector: Locator
-
+  readonly castFileActionBtn: Locator
 
   constructor(page: Page) {
     this.page = page
@@ -20,10 +20,23 @@ export class FilesPage {
     this.jsonViewerBtn = this.page.locator('.oc-files-actions-json-viewer-trigger')
     this.resourceActionDropDownBtn = this.page.locator('.resource-table-btn-action-dropdown')
     this.jsonViewerSelector = this.page.locator('#json-viewer')
+    this.castFileActionBtn = this.page.locator('[data-testid="action-label"] :text-is("Cast")')
   }
 
   getResourceNameSelector(resource: string): Locator {
-    return this.page.locator(`#files-space-table [data-test-resource-name="${resource}"]`);
+    return this.page.locator(`.oc-resource-link [data-test-resource-name="${resource}"]`)
+  }
+
+  async openFileContextMenu(resource: string = '') {
+    if (!resource) {
+      await this.resourceActionDropDownBtn.click()
+      return
+    }
+    const resourceLocator = this.getResourceNameSelector(resource)
+    const rowLocator = this.page
+      .locator('.has-item-context-menu tr')
+      .filter({ has: resourceLocator })
+    await rowLocator.locator('.resource-table-btn-action-dropdown').click()
   }
 
   async extractZip(file: string) {
@@ -51,8 +64,8 @@ export class FilesPage {
     await folderLocator.click()
   }
 
-  async openJsonFile(){
-    await this.resourceActionDropDownBtn.click()
+  async openJsonFile(jsonFIle: string) {
+    await this.openFileContextMenu(jsonFIle)
     await this.jsonViewerBtn.click()
   }
 }
\ No newline at end of file


Reviews:
user: login: saw-jan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-29T07:32:26Z
user: login: amrita-shrestha
body: better if you added screencast of e2e tests. IT help viewer to grab idea what you are testing because all of us doesn't work much on web extension
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-29T09:54:44Z
user: login: saw-jan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-29T09:59:12Z
user: login: saw-jan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-29T09:59:36Z
user: login: saw-jan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-29T10:05:25Z
user: login: saw-jan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-29T10:06:37Z
user: login: saw-jan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-29T10:06:57Z
user: login: amrita-shrestha
state: APPROVED
author_association: NONE
submitted_at: 2025-05-30T10:23:25Z

Review comments:
diff_hunk: @@ -0,0 +1,26 @@
+import { test, Page, expect } from '@playwright/test'
+import { FilesAppBar } from '../../../../support/pages/filesAppBarActions'
+import { loginAsUser, logout } from '../../../../support/helpers/authHelper'
+import { FilesPage } from '../../../../support/pages/filesPage'
+
+let adminPage: Page
+
+test.beforeEach(async ({ browser }) => {
+  const admin = await loginAsUser(browser, 'admin', 'admin')
+  adminPage = admin.page
+})
+
+test.afterEach(async () => {
+  const filesPage = new FilesPage(adminPage)
+  await filesPage.deleteAllFromPersonal()
+  await logout(adminPage)
+})
+
+test.fail('check cast file-action', async () => {
path: packages/web-app-cast/tests/e2e/castFileAction.spec.ts
user: login: saw-jan
body: the extension doesn't work with firefox, webkit and headless chromium
See https://github.com/owncloud/web-extensions/issues/187
created_at: 2025-05-29T07:32:26Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,7 @@ directives:
   script-src:
     - "'self'"
     - "'unsafe-inline'"
+    - 'https://www.gstatic.com/'
path: dev/docker/csp.yaml
user: login: amrita-shrestha
body: both works?
created_at: 2025-05-29T09:49:57Z
author_association: NONE
diff_hunk: @@ -0,0 +1,26 @@
+import { test, Page, expect } from '@playwright/test'
+import { FilesAppBar } from '../../../../support/pages/filesAppBarActions'
+import { loginAsUser, logout } from '../../../../support/helpers/authHelper'
+import { FilesPage } from '../../../../support/pages/filesPage'
+
+let adminPage: Page
+
+test.beforeEach(async ({ browser }) => {
+  const admin = await loginAsUser(browser, 'admin', 'admin')
+  adminPage = admin.page
+})
+
+test.afterEach(async () => {
+  const filesPage = new FilesPage(adminPage)
+  await filesPage.deleteAllFromPersonal()
+  await logout(adminPage)
+})
+
+test.fail('check cast file-action', async () => {
path: packages/web-app-cast/tests/e2e/castFileAction.spec.ts
user: login: amrita-shrestha
body: such information should be added in description for easy access in future never add such information in comment 
created_at: 2025-05-29T09:51:07Z
author_association: NONE
diff_hunk: @@ -108,7 +109,21 @@ export default defineConfig({
       testDir: './packages/web-app-external-sites/tests/e2e',
       use: { ...devices['Desktop Safari'], browserName: 'webkit', ignoreHTTPSErrors: true },
     },
-
+    {
+      name: 'cast-chromium',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Chrome'], browserName: 'chromium', ignoreHTTPSErrors: true },
+    },
+    {
+      name: 'cast-firefox',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Firefox'], browserName: 'firefox', ignoreHTTPSErrors: true },
+    },
+    {
+      name: 'cast-webkit',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Safari'], browserName: 'webkit', ignoreHTTPSErrors: true },
+    }
path: playwright.config.ts
user: login: amrita-shrestha
body: do we need to add this two 
created_at: 2025-05-29T09:52:58Z
author_association: NONE
diff_hunk: @@ -108,7 +109,21 @@ export default defineConfig({
       testDir: './packages/web-app-external-sites/tests/e2e',
       use: { ...devices['Desktop Safari'], browserName: 'webkit', ignoreHTTPSErrors: true },
     },
-
+    {
+      name: 'cast-chromium',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Chrome'], browserName: 'chromium', ignoreHTTPSErrors: true },
+    },
+    {
+      name: 'cast-firefox',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Firefox'], browserName: 'firefox', ignoreHTTPSErrors: true },
+    },
+    {
+      name: 'cast-webkit',
+      testDir: './packages/web-app-cast/tests/e2e',
+      use: { ...devices['Desktop Safari'], browserName: 'webkit', ignoreHTTPSErrors: true },
+    }
path: playwright.config.ts
user: login: saw-jan
body: these are required.
created_at: 2025-05-29T09:59:11Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,7 @@ directives:
   script-src:
     - "'self'"
     - "'unsafe-inline'"
+    - 'https://www.gstatic.com/'
path: dev/docker/csp.yaml
user: login: saw-jan
body: both?
created_at: 2025-05-29T09:59:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,26 @@
+import { test, Page, expect } from '@playwright/test'
+import { FilesAppBar } from '../../../../support/pages/filesAppBarActions'
+import { loginAsUser, logout } from '../../../../support/helpers/authHelper'
+import { FilesPage } from '../../../../support/pages/filesPage'
+
+let adminPage: Page
+
+test.beforeEach(async ({ browser }) => {
+  const admin = await loginAsUser(browser, 'admin', 'admin')
+  adminPage = admin.page
+})
+
+test.afterEach(async () => {
+  const filesPage = new FilesPage(adminPage)
+  await filesPage.deleteAllFromPersonal()
+  await logout(adminPage)
+})
+
+test.fail('check cast file-action', async () => {
path: packages/web-app-cast/tests/e2e/castFileAction.spec.ts
user: login: saw-jan
body: updated in the description
created_at: 2025-05-29T10:05:25Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,7 @@ directives:
   script-src:
     - "'self'"
     - "'unsafe-inline'"
+    - 'https://www.gstatic.com/'
path: dev/docker/csp.yaml
user: login: saw-jan
body: adding `'https://www.gstatic.com/'` allows to use `gstatic.com` host as script source
created_at: 2025-05-29T10:06:37Z
author_association: MEMBER
diff_hunk: @@ -32,6 +32,7 @@ directives:
   script-src:
     - "'self'"
     - "'unsafe-inline'"
+    - 'https://www.gstatic.com/'
path: dev/docker/csp.yaml
user: login: saw-jan
body: this is kind of an allow list
created_at: 2025-05-29T10:06:57Z
author_association: MEMBER

PR comments:
user: login: CLAassistant
created_at: 2025-05-29T07:35:38Z
body: [![CLA assistant check](https://cla-assistant.io/pull/badge/signed)](https://cla-assistant.io/owncloud/web-extensions?pullRequest=186) <br/>All committers have signed the CLA.
author_association: NONE
user: login: saw-jan
created_at: 2025-05-29T10:00:43Z
body: >better if you added screencast of e2e tests. IT help viewer to grab idea what you are testing because all of us doesn't work much on web extension

we only test the functionality of the extension. testing actual screen-casting is out of the scope (we won't test the third party features) and testing it might not be even possible with automation and headless browsers
author_association: MEMBER