Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add end to end tests for app store #11664

Merged
merged 1 commit into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/web-app-app-store/src/views/AppDetails.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<template>
<div class="app-details oc-card oc-card-default oc-card-rounded">
<div class="oc-p-xs">
<router-link :to="{ name: `${APPID}-list` }" class="oc-flex oc-flex-middle">
<router-link :to="{ name: `${APPID}-list` }" class="oc-flex oc-flex-middle app-details-back">
<oc-icon name="arrow-left-s" fill-type="line" />
<span v-text="$gettext('Back to list')" />
</router-link>
</div>
<app-image-gallery :app="app" :show-pagination="true" />
<div class="app-content oc-card-body oc-p">
<div class="oc-flex oc-flex-middle">
<h2 class="oc-my-s oc-text-truncate">{{ app.name }}</h2>
<h2 class="oc-my-s oc-text-truncate app-details-title">{{ app.name }}</h2>
<span class="oc-ml-s oc-text-muted oc-text-small oc-mt-s">
v{{ app.mostRecentVersion.version }}
</span>
Expand Down
2 changes: 1 addition & 1 deletion packages/web-app-app-store/src/views/AppList.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="app-list oc-mb-m">
<h2 class="oc-mt-rm">
<h2 class="oc-mt-rm app-list-headline">
{{ $gettext('App Store') }}
<app-contextual-helper />
</h2>
Expand Down
13 changes: 13 additions & 0 deletions tests/e2e/cucumber/features/app-store/details.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Feature: details

Scenario: Apps can be viewed and downloaded
When "Admin" logs in
And "Admin" navigates to the app store
Then "Admin" should see the app store
When "Admin" clicks on the app "Development boilerplate"
Then "Admin" should see the app details of "Development boilerplate"
And "Admin" downloads app version "0.1.0"
When "Admin" navigates back to the app store overview
Then "Admin" should see the app store
And "Admin" downloads the latest version of the app "Development boilerplate"
And "Admin" logs out
29 changes: 29 additions & 0 deletions tests/e2e/cucumber/features/app-store/discovery.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Feature: discovery

Scenario: apps can be searched and downloaded
When "Admin" logs in
And "Admin" navigates to the app store
Then "Admin" should see the app store
And "Admin" should see the following apps
| app |
| Draw.io |
| JSON Viewer |
| Unzip |
When "Admin" enters the search term "draw"
Then "Admin" should see the following apps
| app |
| Draw.io |
When "Admin" clicks on the tag "viewer" of the app "Draw.io"
Then "Admin" should see the following apps
| app |
| JSON Viewer |
| Draw.io |
When "Admin" clicks on the app "JSON Viewer"
Then "Admin" should see the app details of "JSON Viewer"
When "Admin" clicks on the tag "viewer"
Then "Admin" should see the app store
Then "Admin" should see the following apps
| app |
| JSON Viewer |
| Draw.io |
And "Admin" logs out
105 changes: 105 additions & 0 deletions tests/e2e/cucumber/steps/ui/appStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { DataTable, Then, When } from '@cucumber/cucumber'
import { World } from '../../environment'
import { objects } from '../../../support'
import { expect } from '@playwright/test'

When(
'{string} navigates to the app store',
async function (this: World, stepUser: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
await pageObject.openAppStore()
}
)

Then(
'{string} should see the app store',
async function (this: World, stepUser: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
await pageObject.waitForAppStoreIsVisible()
}
)

Then(
'{string} should see the following apps(s)',
async function (this: World, stepUser: string, stepTable: DataTable): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
const apps = await pageObject.getAppsList()
for (const { app } of stepTable.hashes()) {
expect(apps).toContain(app)
}
}
)

When(
'{string} enters the search term {string}',
async function (this: World, stepUser: string, searchTerm: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
await pageObject.setSearchTerm(searchTerm)
}
)

When(
'{string} clicks on the tag {string} of the app {string}',
async function (this: World, stepUser: string, tag: string, app: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
await pageObject.selectAppTag({ tag, app })
}
)

When(
'{string} clicks on the tag {string}',
async function (this: World, stepUser: string, tag: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
await pageObject.selectTag(tag)
}
)

When(
'{string} clicks on the app {string}',
async function (this: World, stepUser: string, app: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
await pageObject.selectApp(app)
}
)

Then(
'{string} should see the app details of {string}',
async function (this: World, stepUser: string, app: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
await pageObject.waitForAppDetailsIsVisible(app)
}
)

Then(
'{string} downloads app version {string}',
async function (this: World, stepUser: string, version: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
expect(await pageObject.downloadAppVersion(version)).toContain(version)
}
)
Then(
'{string} downloads the latest version of the app {string}',
async function (this: World, stepUser: string, app: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
expect(await pageObject.downloadApp(app)).toBeDefined()
}
)

When(
'{string} navigates back to the app store overview',
async function (this: World, stepUser: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.appStore.AppStore({ page })
await pageObject.navigateToAppStoreOverview()
}
)
87 changes: 87 additions & 0 deletions tests/e2e/support/objects/app-store/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Download, Page } from '@playwright/test'
import util from 'util'

const selectors = {
appLoadingSpinner: '#app-loading-spinner',
appSwitcherButton: '#_appSwitcherButton',
appStoreMenuButton: 'data-test-id=app.app-store.menuItem',
downloadButton: '//a[contains(., "%s")]/ancestor::li//button[.//span[text()="Download"]]',
downloadVersionButton: '//tr[@data-item-id="%s"]//button[.//span[text()="Download"]]',
appStoreHeadline: '.app-list-headline',
appTileTitle: '.app-tile-title',
selectAppTitle: '//a[contains(.,"%s")]',
appDetailsBack: '.app-details-back',
appDetailsTitle: '//h2[contains(@class, "app-details-title")][text()="%s"]',
appsFilter: '#apps-filter',
tag: '//button[contains(@class,"oc-tag")][span[text()="%s"]]',
appTag: '//a[contains(.,"%s")]/following::button[contains(@class,"oc-tag")][span[text()="%s"]]'
}

export const openAppStore = async (args: { page: Page }): Promise<void> => {
const { page } = args
await page.locator(selectors.appSwitcherButton).click()
await page.locator(selectors.appStoreMenuButton).click()
await page.locator(selectors.appLoadingSpinner).waitFor({ state: 'detached' })
}
export const navigateToAppStoreOverview = async (args: { page: Page }): Promise<void> => {
const { page } = args
await page.locator(selectors.appDetailsBack).click()
await page.locator(selectors.appDetailsBack).waitFor({ state: 'detached' })
}

export const waitForAppStoreIsVisible = (args: { page: Page }): Promise<void> => {
const { page } = args
return page.locator(selectors.appStoreHeadline).waitFor()
}

export const getAppsList = (args: { page: Page }): Promise<string[]> => {
const { page } = args
return page.locator(selectors.appTileTitle).allTextContents()
}

export const setSearchTerm = (args: { page: Page; searchTerm: string }): Promise<void> => {
const { page, searchTerm } = args
return page.locator(selectors.appsFilter).fill(searchTerm)
}

export const selectAppTag = (args: { page: Page; app: string; tag: string }): Promise<void> => {
const { page, app, tag } = args
return page.locator(util.format(selectors.appTag, app, tag)).click()
}
export const selectTag = (args: { page: Page; tag: string }): Promise<void> => {
const { page, tag } = args
return page.locator(util.format(selectors.tag, tag)).click()
}

export const selectApp = (args: { page: Page; app: string }): Promise<void> => {
const { page, app } = args
return page.locator(util.format(selectors.selectAppTitle, app)).click()
}

export const waitForAppDetailsIsVisible = (args: { page: Page; app }): Promise<void> => {
const { page, app } = args
return page.locator(util.format(selectors.appDetailsTitle, app)).waitFor()
}

export const downloadAppVersion = async (args: {
page: Page
version: string
}): Promise<string> => {
const { page, version } = args
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator(util.format(selectors.downloadVersionButton, version)).click()
])

return download.suggestedFilename()
}

export const downloadApp = async (args: { page: Page; app: string }): Promise<Download> => {
const { page, app } = args
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator(util.format(selectors.downloadButton, app)).click()
])

return download
}
54 changes: 54 additions & 0 deletions tests/e2e/support/objects/app-store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Download, Page } from '@playwright/test'
import * as po from './actions'

export class AppStore {
#page: Page

constructor({ page }: { page: Page }) {
this.#page = page
}

async openAppStore(): Promise<void> {
await po.openAppStore({ page: this.#page })
}

async waitForAppStoreIsVisible(): Promise<void> {
return po.waitForAppStoreIsVisible({ page: this.#page })
}

async getAppsList(): Promise<string[]> {
return po.getAppsList({ page: this.#page })
}

async setSearchTerm(searchTerm: string): Promise<void> {
return po.setSearchTerm({ page: this.#page, searchTerm })
}

async selectAppTag({ app, tag }: { app: string; tag: string }): Promise<void> {
return po.selectAppTag({ page: this.#page, app, tag })
}

async selectTag(tag): Promise<void> {
return po.selectTag({ page: this.#page, tag })
}

async selectApp(app: string): Promise<void> {
return po.selectApp({ page: this.#page, app })
}

async waitForAppDetailsIsVisible(app: string): Promise<void> {
return po.waitForAppDetailsIsVisible({ page: this.#page, app })
}

async downloadApp(app: string): Promise<Download> {
return po.downloadApp({ page: this.#page, app })
}

async downloadAppVersion(version: string): Promise<string> {
return po.downloadAppVersion({ page: this.#page, version })
}

async navigateToAppStoreOverview(): Promise<void> {
await po.navigateToAppStoreOverview({ page: this.#page })
}
}
1 change: 1 addition & 0 deletions tests/e2e/support/objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * as applicationAdminSettings from './app-admin-settings'
export * as runtime from './runtime'
export * as account from './account'
export * as urlNavigation from './url-navigation'
export * as appStore from './app-store'