@appsemble/e2e 0.34.21 → 0.35.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/fixtures/expect/training/index.js +4 -1
- package/fixtures/test/app/index.d.ts +24 -2
- package/fixtures/test/app/index.js +40 -1
- package/fixtures/test/app-collection/index.d.ts +14 -7
- package/fixtures/test/app-collection/index.js +46 -32
- package/fixtures/test/index.d.ts +1 -1
- package/fixtures/test/index.js +2 -1
- package/fixtures/test/live-app/index.d.ts +4 -2
- package/fixtures/test/live-app/index.js +11 -5
- package/fixtures/test/organization/index.d.ts +26 -0
- package/fixtures/test/organization/index.js +35 -0
- package/fixtures/test/studio/index.d.ts +32 -0
- package/fixtures/test/studio/index.js +26 -1
- package/fixtures/test/test-utils/index.d.ts +5 -1
- package/fixtures/test/test-utils/index.js +15 -1
- package/package.json +4 -4
- package/playwright.config.js +21 -1
- package/tests/demo-apps/empty.spec.js +26 -0
- package/tests/{template-apps → demo-apps}/holidays.spec.js +13 -1
- package/tests/{template-apps → demo-apps}/notes.spec.js +12 -3
- package/tests/{template-apps → demo-apps}/person.spec.js +15 -1
- package/tests/static-ui/apps.spec.js +37 -0
- package/tests/static-ui/blocks.spec.js +35 -0
- package/tests/static-ui/individual-app.spec.js +69 -0
- package/tests/static-ui/organizations.spec.js +41 -0
- package/tests/static-ui/training.spec.js +39 -0
- package/tests/studio/app-collections/app-collections.spec.js +127 -103
- package/tests/studio/individual-app.spec.d.ts +1 -0
- package/tests/studio/individual-app.spec.js +51 -0
- package/tests/studio/organizations.spec.js +31 -24
- package/tests/studio/payments/subscription-flow.spec.d.ts +1 -0
- package/tests/studio/payments/subscription-flow.spec.js +68 -0
- package/tests/studio/trainings.spec.d.ts +1 -0
- package/tests/studio/trainings.spec.js +105 -0
- package/tests/studio/apps/app.spec.js +0 -79
- package/tests/studio/apps.spec.js +0 -30
- package/tests/studio/blocks.spec.js +0 -20
- package/tests/studio/trainings/constants.d.ts +0 -4
- package/tests/studio/trainings/constants.js +0 -5
- package/tests/studio/trainings/logged-in.spec.js +0 -93
- package/tests/studio/trainings/logged-out.spec.js +0 -42
- package/tests/template-apps/empty.spec.js +0 -14
- /package/tests/{template-apps → demo-apps}/empty.spec.d.ts +0 -0
- /package/tests/{template-apps → demo-apps}/holidays.spec.d.ts +0 -0
- /package/tests/{template-apps → demo-apps}/notes.spec.d.ts +0 -0
- /package/tests/{template-apps → demo-apps}/person.spec.d.ts +0 -0
- /package/tests/{studio → static-ui}/apps.spec.d.ts +0 -0
- /package/tests/{studio → static-ui}/blocks.spec.d.ts +0 -0
- /package/tests/{studio → static-ui}/docs.spec.d.ts +0 -0
- /package/tests/{studio → static-ui}/docs.spec.js +0 -0
- /package/tests/{studio/apps/app.spec.d.ts → static-ui/individual-app.spec.d.ts} +0 -0
- /package/tests/{studio/trainings/logged-in.spec.d.ts → static-ui/organizations.spec.d.ts} +0 -0
- /package/tests/{studio/trainings/logged-out.spec.d.ts → static-ui/training.spec.d.ts} +0 -0
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
1
2
|
import { expect, test } from '../../index.js';
|
|
3
|
+
let appId;
|
|
4
|
+
let organizationId;
|
|
2
5
|
test.describe('Notes', () => {
|
|
3
|
-
test.beforeAll(async ({
|
|
4
|
-
const
|
|
6
|
+
test.beforeAll(async ({ createApp, createOrganization, giveAppConsent, randomTestId }) => {
|
|
7
|
+
const appDefinition = await readFile('../../apps/notes/app-definition.yaml', 'utf8');
|
|
8
|
+
organizationId = (await createOrganization({ id: randomTestId() })).id;
|
|
9
|
+
appId = (await createApp(organizationId, appDefinition)).id;
|
|
5
10
|
await giveAppConsent(appId);
|
|
6
11
|
});
|
|
12
|
+
test.afterAll(async ({ deleteApp, deleteOrganization }) => {
|
|
13
|
+
await deleteApp(appId);
|
|
14
|
+
await deleteOrganization(organizationId);
|
|
15
|
+
});
|
|
7
16
|
test.beforeEach(async ({ loginAppAppsembleOAuth, page, visitApp }) => {
|
|
8
|
-
await visitApp(
|
|
17
|
+
await visitApp(appId);
|
|
9
18
|
await loginAppAppsembleOAuth();
|
|
10
19
|
await page.waitForURL('**/notes');
|
|
11
20
|
});
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
1
2
|
import { expect, test } from '../../index.js';
|
|
3
|
+
let appId;
|
|
4
|
+
let organizationId;
|
|
2
5
|
test.describe('Person', () => {
|
|
6
|
+
test.beforeAll(async ({ createApp, createOrganization, randomTestId, uploadAppMessages }) => {
|
|
7
|
+
const appDefinition = await readFile('../../apps/person/app-definition.yaml', 'utf8');
|
|
8
|
+
const englishTranslations = JSON.parse(await readFile('../../apps/person/i18n/en.json', 'utf8'));
|
|
9
|
+
organizationId = (await createOrganization({ id: randomTestId() })).id;
|
|
10
|
+
appId = (await createApp(organizationId, appDefinition)).id;
|
|
11
|
+
await uploadAppMessages(appId, 'en', englishTranslations);
|
|
12
|
+
});
|
|
13
|
+
test.afterAll(async ({ deleteApp, deleteOrganization }) => {
|
|
14
|
+
await deleteApp(appId);
|
|
15
|
+
await deleteOrganization(organizationId);
|
|
16
|
+
});
|
|
3
17
|
test.beforeEach(async ({ page, visitApp }) => {
|
|
4
|
-
await visitApp(
|
|
18
|
+
await visitApp(appId);
|
|
5
19
|
await page.waitForURL('**/person-registration-form');
|
|
6
20
|
});
|
|
7
21
|
test('should submit a new person and view it', async ({ page }) => {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expect, test } from '../../index.js';
|
|
2
|
+
const { BOT_ACCOUNT_EMAIL, BOT_ACCOUNT_PASSWORD } = process.env;
|
|
3
|
+
test.describe('Apps', () => {
|
|
4
|
+
// Prevent worker from being logged in automatically
|
|
5
|
+
test.use({ storageState: { cookies: [], origins: [] } });
|
|
6
|
+
test.beforeEach(async ({ page }) => {
|
|
7
|
+
await page.route('**/apps?language=en', async (route) => {
|
|
8
|
+
await route.fulfill({ path: 'mock-data/app-list.json' });
|
|
9
|
+
});
|
|
10
|
+
await page.route('**/api/apps/0?language=en', async (route) => {
|
|
11
|
+
await route.fulfill({ path: 'mock-data/test-app-details.json' });
|
|
12
|
+
});
|
|
13
|
+
await page.goto('/en/apps');
|
|
14
|
+
});
|
|
15
|
+
test('should show the page header', async ({ page }) => {
|
|
16
|
+
await expect(page.getByRole('heading', { name: 'All Apps', exact: true })).toBeVisible();
|
|
17
|
+
});
|
|
18
|
+
test('should display “My Apps” when logged in', async ({ loginUser, page }) => {
|
|
19
|
+
expect(BOT_ACCOUNT_EMAIL && BOT_ACCOUNT_PASSWORD).not.toBeUndefined();
|
|
20
|
+
await loginUser(BOT_ACCOUNT_EMAIL, BOT_ACCOUNT_PASSWORD);
|
|
21
|
+
await expect(page.getByRole('heading', { name: 'My Apps', exact: true })).toBeVisible();
|
|
22
|
+
});
|
|
23
|
+
test('should render list of apps', async ({ page }) => {
|
|
24
|
+
await expect(page.getByRole('link', { name: 'Test app Test org' }).first())
|
|
25
|
+
.toMatchAriaSnapshot(`
|
|
26
|
+
- link "Test app Test org":
|
|
27
|
+
- figure:
|
|
28
|
+
- heading "Test app" [level=3]
|
|
29
|
+
- heading "Test org" [level=6]
|
|
30
|
+
`);
|
|
31
|
+
});
|
|
32
|
+
test('should link to app details', async ({ page }) => {
|
|
33
|
+
await page.getByRole('link', { name: 'Test app Test org' }).first().click();
|
|
34
|
+
await expect(page.getByText('Test app description')).toBeVisible();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=apps.spec.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expect, test } from '../../index.js';
|
|
2
|
+
test.describe('Blocks', () => {
|
|
3
|
+
test.beforeEach(async ({ page }) => {
|
|
4
|
+
await page.route('**/api/blocks', async (route) => {
|
|
5
|
+
await route.fulfill({ path: 'mock-data/block-list.json' });
|
|
6
|
+
});
|
|
7
|
+
await page.route('**/api/blocks/@test-org/test-block/versions/0.0.0', async (route) => {
|
|
8
|
+
await route.fulfill({ path: 'mock-data/test-block-details.json' });
|
|
9
|
+
});
|
|
10
|
+
await page.route('**/api/blocks/@test-org/test-block/versions/list', async (route) => {
|
|
11
|
+
await route.fulfill({ json: ['0.0.0'] });
|
|
12
|
+
});
|
|
13
|
+
await page.goto('/en/blocks');
|
|
14
|
+
});
|
|
15
|
+
test('should render a list of blocks', async ({ page }) => {
|
|
16
|
+
await expect(page.getByTitle('@test-org/test-block')).toMatchAriaSnapshot(`
|
|
17
|
+
- banner:
|
|
18
|
+
- figure:
|
|
19
|
+
- heading "test-block" [level=5]
|
|
20
|
+
- heading "@test-org" [level=6]:
|
|
21
|
+
- link "@test-org"
|
|
22
|
+
- text: 0.0.0
|
|
23
|
+
- text: A test block
|
|
24
|
+
- contentinfo:
|
|
25
|
+
- link "View details"
|
|
26
|
+
`);
|
|
27
|
+
});
|
|
28
|
+
test('should link to block details', async ({ page }) => {
|
|
29
|
+
await page.getByTitle('@test-org/test-block').getByText('View details').click();
|
|
30
|
+
await expect(page.getByRole('heading', { name: 'Parameters', exact: true })).toBeVisible();
|
|
31
|
+
await expect(page.getByRole('heading', { name: 'Actions', exact: true })).toBeVisible();
|
|
32
|
+
await expect(page.getByRole('heading', { name: 'Events', exact: true })).toBeVisible();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=blocks.spec.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { expect, test } from '../../index.js';
|
|
2
|
+
const { BOT_ACCOUNT_EMAIL, BOT_ACCOUNT_PASSWORD } = process.env;
|
|
3
|
+
let page;
|
|
4
|
+
test.describe('Individual app', () => {
|
|
5
|
+
test.beforeAll(async ({ browser, loginUserOnPage }) => {
|
|
6
|
+
page = await browser.newPage({ storageState: undefined });
|
|
7
|
+
expect(BOT_ACCOUNT_EMAIL && BOT_ACCOUNT_PASSWORD).not.toBeUndefined();
|
|
8
|
+
await loginUserOnPage(BOT_ACCOUNT_EMAIL, BOT_ACCOUNT_PASSWORD, page);
|
|
9
|
+
});
|
|
10
|
+
test.afterAll(async () => {
|
|
11
|
+
await page.close();
|
|
12
|
+
});
|
|
13
|
+
test.beforeEach(async () => {
|
|
14
|
+
await page.route('**/api/apps/0?language=en', async (route) => {
|
|
15
|
+
await route.fulfill({ path: 'mock-data/test-app-details.json' });
|
|
16
|
+
});
|
|
17
|
+
await page.route('**/api/users/current', async (route) => {
|
|
18
|
+
await route.fulfill({ path: 'mock-data/user-info.json' });
|
|
19
|
+
});
|
|
20
|
+
await page.route('**/api/users/current/organizations', async (route) => {
|
|
21
|
+
await route.fulfill({ path: 'mock-data/organization-list.json' });
|
|
22
|
+
});
|
|
23
|
+
await page.route('**/api/apps/0/resources/test-resource', async (route) => {
|
|
24
|
+
await route.fulfill({ json: [] });
|
|
25
|
+
});
|
|
26
|
+
await page.route('**/api/apps/0/snapshots', async (route) => {
|
|
27
|
+
await route.fulfill({ path: 'mock-data/snapshot-list.json' });
|
|
28
|
+
});
|
|
29
|
+
await page.goto('/en/apps/0/test-app');
|
|
30
|
+
});
|
|
31
|
+
test('should show the app name in the side menu', async () => {
|
|
32
|
+
await expect(page.getByTestId('studio-app-side-menu-name')).toHaveText('test app');
|
|
33
|
+
});
|
|
34
|
+
test('should link to the asset viewer', async ({ clickAppSideMenuItemOnPage }) => {
|
|
35
|
+
await clickAppSideMenuItemOnPage('Assets', page);
|
|
36
|
+
await expect(page.getByText('Upload new asset')).toBeVisible();
|
|
37
|
+
});
|
|
38
|
+
test('should link to resources', async ({ clickAppSideMenuItemOnPage }) => {
|
|
39
|
+
await clickAppSideMenuItemOnPage('Resources', page);
|
|
40
|
+
await expect(page.getByText('This app has the following resources')).toBeVisible();
|
|
41
|
+
});
|
|
42
|
+
test('should link to a specific resource', async ({ clickAppSideMenuItemOnPage }) => {
|
|
43
|
+
await clickAppSideMenuItemOnPage('Resources', page);
|
|
44
|
+
await page.click('li :has-text("Test resource")');
|
|
45
|
+
await expect(page.getByText('Resource: Test resource')).toBeVisible();
|
|
46
|
+
});
|
|
47
|
+
test('should link to the translator tool', async ({ clickAppSideMenuItemOnPage }) => {
|
|
48
|
+
await clickAppSideMenuItemOnPage('Translations', page);
|
|
49
|
+
await expect(page.getByText('Selected language')).toBeVisible();
|
|
50
|
+
});
|
|
51
|
+
test('should link to the notification sender', async ({ clickAppSideMenuItemOnPage }) => {
|
|
52
|
+
await clickAppSideMenuItemOnPage('Notifications', page);
|
|
53
|
+
await expect(page.getByText('Push notifications are currently not enabled in this app.')).toBeVisible();
|
|
54
|
+
});
|
|
55
|
+
test('should link to the snapshots page', async ({ clickAppSideMenuItemOnPage }) => {
|
|
56
|
+
await clickAppSideMenuItemOnPage('Snapshots', page);
|
|
57
|
+
await expect(page.getByRole('heading', { name: 'Snapshots' })).toBeVisible();
|
|
58
|
+
await expect(page.getByRole('listitem').filter({ has: page.getByRole('heading', { name: 'Appsemble' }) })).toBeVisible();
|
|
59
|
+
});
|
|
60
|
+
test('should link to the app settings', async ({ clickAppSideMenuItemOnPage }) => {
|
|
61
|
+
await clickAppSideMenuItemOnPage('Settings', page);
|
|
62
|
+
await expect(page.getByRole('heading', { name: 'App lock' })).toBeVisible();
|
|
63
|
+
});
|
|
64
|
+
test('should link to the app secrets', async ({ clickAppSideMenuItemOnPage }) => {
|
|
65
|
+
await clickAppSideMenuItemOnPage('Secrets', page);
|
|
66
|
+
await expect(page.getByRole('heading', { name: 'Appsemble Login' })).toBeVisible();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=individual-app.spec.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { expect, test } from '../../index.js';
|
|
2
|
+
test.describe('Organizations', () => {
|
|
3
|
+
test.beforeEach(async ({ page }) => {
|
|
4
|
+
await page.route('**/api/organizations', async (route) => {
|
|
5
|
+
await route.fulfill({ path: 'mock-data/organization-list.json' });
|
|
6
|
+
});
|
|
7
|
+
await page.route('**/api/organizations/test-org', async (route) => {
|
|
8
|
+
await route.fulfill({ path: 'mock-data/test-organization-details.json' });
|
|
9
|
+
});
|
|
10
|
+
await page.route('**/api/organizations/test-org/apps?language=en', async (route) => {
|
|
11
|
+
await route.fulfill({ path: 'mock-data/app-list.json' });
|
|
12
|
+
});
|
|
13
|
+
await page.route('**/api/organizations/test-org/blocks', async (route) => {
|
|
14
|
+
await route.fulfill({ path: 'mock-data/block-list.json' });
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
test('should render a list of organizations', async ({ page }) => {
|
|
18
|
+
await page.goto('/en/organizations');
|
|
19
|
+
await expect(page.getByRole('link', { name: 'Test org test-org', exact: true })).toBeVisible();
|
|
20
|
+
});
|
|
21
|
+
test('should link to organization details from organization list', async ({ page }) => {
|
|
22
|
+
await page.goto('/en/organizations');
|
|
23
|
+
await page.getByRole('link', { name: 'Test org test-org', exact: true }).click();
|
|
24
|
+
await expect(page.locator('.card > div').first()).toMatchAriaSnapshot(`
|
|
25
|
+
- banner:
|
|
26
|
+
- heading "Test org" [level=1]
|
|
27
|
+
- heading "test-org" [level=6]
|
|
28
|
+
- paragraph: Organization description
|
|
29
|
+
- link "appsemble.com"
|
|
30
|
+
- link "support@appsemble.com"
|
|
31
|
+
`);
|
|
32
|
+
});
|
|
33
|
+
test('should show lists of apps and blocks that are by the organization', async ({ page }) => {
|
|
34
|
+
await page.goto('/en/organizations/test-org');
|
|
35
|
+
await expect(page.getByRole('heading', { name: 'Apps', exact: true })).toBeVisible();
|
|
36
|
+
await expect(page.getByRole('link', { name: 'Test app Test org' })).toBeVisible();
|
|
37
|
+
await expect(page.getByRole('heading', { name: 'Blocks', exact: true })).toBeVisible();
|
|
38
|
+
await expect(page.getByTitle('@test-org/test-block')).toBeVisible();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=organizations.spec.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { expect, test } from '../../fixtures/index.js';
|
|
2
|
+
test.describe('Training', () => {
|
|
3
|
+
// Prevent worker from being logged in automatically
|
|
4
|
+
test.use({ storageState: { cookies: [], origins: [] } });
|
|
5
|
+
test.beforeEach(async ({ page }) => {
|
|
6
|
+
await page.goto('/en/trainings');
|
|
7
|
+
});
|
|
8
|
+
test("should show 'Training' in breadcrumbs", async ({ page }) => {
|
|
9
|
+
await expect(page.getByLabel('Breadcrumbs').getByRole('listitem').filter({ hasText: 'Training' })).toBeVisible();
|
|
10
|
+
});
|
|
11
|
+
test('should render first chapter in training tree', async ({ page }) => {
|
|
12
|
+
await expect(page.getByTestId('rf__node-introduction')).toBeVisible();
|
|
13
|
+
});
|
|
14
|
+
test('should show banner if user is not logged in', async ({ page }) => {
|
|
15
|
+
await expect(page.getByText('Warning: You are currently not logged in. While logged out you can access all trainings, but progress will not be saved')).toBeVisible();
|
|
16
|
+
});
|
|
17
|
+
test('should have all trainings unlocked if user is not logged in', async ({ page }) => {
|
|
18
|
+
// Wait for initialization since locator.all() does not wait for elements to be visible
|
|
19
|
+
await page.getByTestId('rf__node-introduction').waitFor({ state: 'visible' });
|
|
20
|
+
const trainingNodes = await page.locator('.react-flow__node').all();
|
|
21
|
+
expect(trainingNodes.length).toBeGreaterThan(0);
|
|
22
|
+
for (const node of trainingNodes) {
|
|
23
|
+
await expect(node).toHaveChapterStatus('available');
|
|
24
|
+
}
|
|
25
|
+
const blockedNodeTitles = await page.getByTestId('node-title-blocked').all();
|
|
26
|
+
expect(blockedNodeTitles.length).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
test('should navigate to training once the link is clicked', async ({ page }) => {
|
|
29
|
+
await page.getByTestId('rf__node-what-is-appsemble').getByRole('link').click();
|
|
30
|
+
await expect(page.getByRole('heading', { name: 'Appsemble Introduction ' })).toBeVisible();
|
|
31
|
+
});
|
|
32
|
+
test('should allow user to click through chapter trainings', async ({ page }) => {
|
|
33
|
+
await page.getByTestId('rf__node-what-is-appsemble').getByRole('link').click();
|
|
34
|
+
await page.getByRole('button', { name: 'Click to complete' }).click();
|
|
35
|
+
await page.getByRole('button', { name: 'To "Get familiar with the studio"' }).click();
|
|
36
|
+
await expect(page.getByRole('heading', { name: 'Get familiar with the studio' })).toBeVisible();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=training.spec.js.map
|
|
@@ -1,55 +1,71 @@
|
|
|
1
1
|
import { expect, test } from '../../../index.js';
|
|
2
|
-
|
|
2
|
+
let organizationId;
|
|
3
3
|
let appCollectionName;
|
|
4
|
+
let appCollectionId;
|
|
4
5
|
test.describe('App Collections', () => {
|
|
5
|
-
test.
|
|
6
|
-
await
|
|
6
|
+
test.beforeAll(async ({ createOrganization, randomTestId }) => {
|
|
7
|
+
organizationId = (await createOrganization({ id: randomTestId() })).id;
|
|
8
|
+
});
|
|
9
|
+
test.beforeEach(({ randomTestId }) => {
|
|
10
|
+
appCollectionName = randomTestId(1);
|
|
7
11
|
});
|
|
8
12
|
test.afterEach(async ({ deleteAppCollection }) => {
|
|
9
|
-
if (
|
|
10
|
-
await deleteAppCollection(
|
|
13
|
+
if (appCollectionId !== undefined) {
|
|
14
|
+
await deleteAppCollection(appCollectionId);
|
|
15
|
+
appCollectionId = undefined;
|
|
11
16
|
}
|
|
12
17
|
});
|
|
13
|
-
test(
|
|
14
|
-
|
|
18
|
+
test.afterAll(async ({ deleteOrganization }) => {
|
|
19
|
+
await deleteOrganization(organizationId);
|
|
20
|
+
});
|
|
21
|
+
test('should create an app collection', async ({ page }) => {
|
|
15
22
|
await page.route('/test-assets/side.png', (route) => {
|
|
16
23
|
route.fulfill({ path: 'mock-data/tux.png' });
|
|
17
24
|
});
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
page.on('response', async (response) => {
|
|
26
|
+
if (response.request().method() === 'POST' &&
|
|
27
|
+
/\/api\/organizations\/\d+\/app-collections$/.test(response.url())) {
|
|
28
|
+
appCollectionId = (await response.json()).id;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
await page.goto(`/en/organizations/${organizationId}/collections`);
|
|
32
|
+
await page.getByRole('button', { name: 'Create new app collection' }).click();
|
|
33
|
+
await test.step('Fill in app collection details', async () => {
|
|
34
|
+
await page.getByLabel('Name', { exact: true }).fill(appCollectionName);
|
|
35
|
+
await page.getByLabel('Header image').setInputFiles('mock-data/retail.png');
|
|
36
|
+
await page.getByLabel('Expert name').fill('John Doe');
|
|
37
|
+
await page
|
|
38
|
+
.getByLabel('Expert description')
|
|
39
|
+
.fill('Lorem ipsum\n\n**text in bold**\n\n');
|
|
40
|
+
await page.getByLabel('Expert photo').setInputFiles('mock-data/tux.png');
|
|
41
|
+
// Avatar editor
|
|
42
|
+
await page.getByRole('button', { name: 'Save' }).click();
|
|
43
|
+
});
|
|
44
|
+
await page.getByRole('button', { name: 'Create app collection' }).click();
|
|
45
|
+
await page.getByRole('link', { name: `${appCollectionName} John Doe` }).click();
|
|
20
46
|
await expect(page.getByLabel('Breadcrumbs').locator('li:nth-child(2)')).toHaveText(appCollectionName);
|
|
21
|
-
await expect(page.locator('header.has-background-primary.is-flex.is-flex-wrap-wrap')).toHaveScreenshot({
|
|
22
|
-
timeout: 5000,
|
|
47
|
+
await expect(page.locator('header.has-background-primary.is-flex.is-flex-wrap-wrap')).toHaveScreenshot('collection-header.png', {
|
|
23
48
|
maxDiffPixelRatio: 0.02,
|
|
49
|
+
mask: [page.getByText(appCollectionName)],
|
|
24
50
|
});
|
|
51
|
+
await expect(page.getByRole('heading', { name: appCollectionName })).toBeVisible();
|
|
25
52
|
await page.getByRole('link', { name: 'Expert' }).click();
|
|
26
53
|
await page.getByRole('img', { name: 'side image' }).waitFor({ state: 'visible' });
|
|
27
|
-
|
|
28
|
-
await expect(page.locator('section:has(div)')).toHaveScreenshot({
|
|
29
|
-
timeout: 5000,
|
|
54
|
+
await expect(page.locator('section:has(div)')).toHaveScreenshot('expert-page.png', {
|
|
30
55
|
maxDiffPixelRatio: 0.02,
|
|
31
56
|
});
|
|
32
57
|
});
|
|
33
|
-
test('should
|
|
34
|
-
|
|
35
|
-
await
|
|
36
|
-
await page.
|
|
37
|
-
await page.getByRole('heading', { name: 'App collections' }).waitFor({ state: 'visible' });
|
|
38
|
-
await expect(page.getByRole('link', { name: appCollectionName })).toBeVisible({
|
|
39
|
-
timeout: 5000,
|
|
40
|
-
});
|
|
58
|
+
test('should hide a private app collection if user is not in organization', async ({ browser, createAppCollection, page, }) => {
|
|
59
|
+
appCollectionId = (await createAppCollection(organizationId, appCollectionName, 'private', 'John Doe', 'mock-data/retail.png', 'mock-data/tux.png', 'Lorem ipsum')).id;
|
|
60
|
+
await page.goto(`/en/organizations/${organizationId}/collections`);
|
|
61
|
+
await expect(page.getByRole('link', { name: appCollectionName })).toBeVisible();
|
|
41
62
|
const loggedOutPage = await browser.newPage({ storageState: undefined });
|
|
42
|
-
await loggedOutPage.goto(
|
|
43
|
-
await loggedOutPage
|
|
44
|
-
.getByRole('heading', { name: 'App collections' })
|
|
45
|
-
.waitFor({ state: 'visible' });
|
|
46
|
-
await expect(loggedOutPage.getByRole('link', { name: appCollectionName })).toBeHidden({
|
|
47
|
-
timeout: 5000,
|
|
48
|
-
});
|
|
63
|
+
await loggedOutPage.goto(`/en/organizations/${organizationId}/collections`);
|
|
64
|
+
await expect(loggedOutPage.getByRole('link', { name: appCollectionName })).toBeHidden();
|
|
49
65
|
});
|
|
50
|
-
test('should edit an app collection', async ({
|
|
51
|
-
|
|
52
|
-
await
|
|
66
|
+
test('should edit an app collection', async ({ createAppCollection, page }) => {
|
|
67
|
+
appCollectionId = (await createAppCollection(organizationId, appCollectionName, 'public', 'John Doe', 'mock-data/retail.png', 'mock-data/tux.png', 'Lorem ipsum')).id;
|
|
68
|
+
await page.goto(`/en/organizations/${organizationId}/collections`);
|
|
53
69
|
await page.getByRole('link', { name: appCollectionName }).click();
|
|
54
70
|
await page.getByRole('link', { name: 'Settings' }).click();
|
|
55
71
|
const oldName = appCollectionName;
|
|
@@ -58,101 +74,109 @@ test.describe('App Collections', () => {
|
|
|
58
74
|
await page.getByLabel('Expert name').fill('Jane Doe');
|
|
59
75
|
await page.getByLabel('Expert description').fill('Hello world');
|
|
60
76
|
await page.getByRole('button', { name: 'Save' }).click();
|
|
61
|
-
await page.getByRole('link', { name:
|
|
77
|
+
await page.getByRole('link', { name: ' Apps' }).click();
|
|
62
78
|
await expect(page.getByRole('heading', { name: oldName })).toBeHidden();
|
|
63
|
-
await expect(page.getByRole('heading', { name: appCollectionName })).toBeVisible();
|
|
64
79
|
await page.getByRole('link', { name: 'Expert Jane Doe' }).click();
|
|
65
80
|
await expect(page.locator('h1').filter({ hasText: 'Jane Doe' })).toBeVisible();
|
|
66
81
|
});
|
|
67
|
-
test('should delete an app collection', async ({
|
|
68
|
-
|
|
69
|
-
await
|
|
70
|
-
await page.getByRole('link', { name:
|
|
82
|
+
test('should delete an app collection', async ({ createAppCollection, page }) => {
|
|
83
|
+
appCollectionId = (await createAppCollection(organizationId, appCollectionName, 'public', 'John Doe', 'mock-data/retail.png', 'mock-data/tux.png', 'Lorem ipsum')).id;
|
|
84
|
+
await page.goto(`/en/organizations/${organizationId}/collections`);
|
|
85
|
+
await page.getByRole('link', { name: appCollectionName }).click();
|
|
71
86
|
await page.getByRole('link', { name: 'Settings' }).click();
|
|
72
87
|
await page.getByRole('button', { name: 'Delete app collection' }).click();
|
|
73
88
|
await page.getByRole('button', { name: 'Delete app collection', exact: true }).click();
|
|
74
|
-
await page.
|
|
75
|
-
await expect(page.getByRole('link', { name: appCollectionName })).toBeHidden(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Set to undefined so the afterEach doesn't try to delete a non-existent collection
|
|
79
|
-
appCollectionName = undefined;
|
|
89
|
+
await expect(page.getByText('Successfully deleted app')).toBeVisible();
|
|
90
|
+
await expect(page.getByRole('link', { name: appCollectionName })).toBeHidden();
|
|
91
|
+
// Make sure the afterEach hook doesn't try to delete the already deleted collection
|
|
92
|
+
appCollectionId = undefined;
|
|
80
93
|
});
|
|
81
|
-
test('should add an app to an app collection', async ({
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
test('should add an app to an app collection', async ({ createApp, createAppCollection, deleteApp, page, randomTestId, }) => {
|
|
95
|
+
appCollectionId = (await createAppCollection(organizationId, appCollectionName, 'public', 'John Doe', 'mock-data/retail.png', 'mock-data/tux.png', 'Lorem ipsum')).id;
|
|
96
|
+
const appName = `Test app ${randomTestId()}`;
|
|
97
|
+
const { id } = await createApp(organizationId, `
|
|
98
|
+
name: ${appName}
|
|
99
|
+
defaultPage: Test Page
|
|
100
|
+
pages:
|
|
101
|
+
- name: Test Page
|
|
102
|
+
blocks:
|
|
103
|
+
- type: data-loader
|
|
104
|
+
version: 0.34.15
|
|
105
|
+
`);
|
|
106
|
+
await page.goto(`/en/apps/${id}`);
|
|
86
107
|
await page.click('#app-options-menu');
|
|
87
108
|
await page.getByRole('button', { name: 'Add to collection' }).click();
|
|
88
|
-
await page.
|
|
89
|
-
await page
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
await page.goto('/en/collections');
|
|
94
|
-
await page.getByRole('link', { name: appCollectionName }).click();
|
|
95
|
-
await expect(page.getByRole('link', { name: 'Empty' })).toBeVisible();
|
|
109
|
+
await page.getByRole('combobox').selectOption({ label: appCollectionName });
|
|
110
|
+
await page.locator('form').getByRole('button', { name: 'Add to collection' }).click();
|
|
111
|
+
await page.goto(`/en/collections/${appCollectionId}`);
|
|
112
|
+
await expect(page.getByRole('link', { name: appName })).toBeVisible();
|
|
113
|
+
await deleteApp(id);
|
|
96
114
|
});
|
|
97
|
-
test('should remove an app from an app collection', async ({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
await
|
|
110
|
-
await page.
|
|
115
|
+
test('should remove an app from an app collection', async ({ addAppToAppCollection, createApp, createAppCollection, deleteApp, page, randomTestId, }) => {
|
|
116
|
+
appCollectionId = (await createAppCollection(organizationId, appCollectionName, 'public', 'John Doe', 'mock-data/retail.png', 'mock-data/tux.png', 'Lorem ipsum')).id;
|
|
117
|
+
const appName = `Test app ${randomTestId()}`;
|
|
118
|
+
const { id } = await createApp(organizationId, `
|
|
119
|
+
name: ${appName}
|
|
120
|
+
defaultPage: Test Page
|
|
121
|
+
pages:
|
|
122
|
+
- name: Test Page
|
|
123
|
+
blocks:
|
|
124
|
+
- type: data-loader
|
|
125
|
+
version: 0.34.15
|
|
126
|
+
`);
|
|
127
|
+
await addAppToAppCollection(appCollectionId, id);
|
|
128
|
+
await page.goto(`/en/collections/${appCollectionId}`);
|
|
111
129
|
await page.getByRole('button', { name: 'Edit' }).click();
|
|
112
130
|
await page
|
|
113
|
-
.locator('div', { has: page.getByRole('link', { name:
|
|
114
|
-
.
|
|
131
|
+
.locator('div', { has: page.getByRole('link', { name: appName }) })
|
|
132
|
+
.getByTitle('Delete app from collection')
|
|
115
133
|
.click();
|
|
116
134
|
await page.getByRole('button', { name: 'Confirm' }).click();
|
|
117
|
-
await expect(page.getByRole('link', { name:
|
|
135
|
+
await expect(page.getByRole('link', { name: appName })).toBeHidden();
|
|
136
|
+
await deleteApp(id);
|
|
118
137
|
});
|
|
119
|
-
test('should pin an app to an app collection', async ({
|
|
120
|
-
|
|
121
|
-
await
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
await
|
|
141
|
-
await page.
|
|
138
|
+
test('should pin an app to an app collection', async ({ addAppToAppCollection, createApp, createAppCollection, deleteApp, page, randomTestId, }) => {
|
|
139
|
+
appCollectionId = (await createAppCollection(organizationId, appCollectionName, 'public', 'John Doe', 'mock-data/retail.png', 'mock-data/tux.png', 'Lorem ipsum')).id;
|
|
140
|
+
const app1 = await createApp(organizationId, `
|
|
141
|
+
name: Test app ${randomTestId()}
|
|
142
|
+
defaultPage: Test Page
|
|
143
|
+
pages:
|
|
144
|
+
- name: Test Page
|
|
145
|
+
blocks:
|
|
146
|
+
- type: data-loader
|
|
147
|
+
version: 0.34.15
|
|
148
|
+
`);
|
|
149
|
+
const app2 = await createApp(organizationId, `
|
|
150
|
+
name: Test app ${randomTestId()}
|
|
151
|
+
defaultPage: Test Page
|
|
152
|
+
pages:
|
|
153
|
+
- name: Test Page
|
|
154
|
+
blocks:
|
|
155
|
+
- type: data-loader
|
|
156
|
+
version: 0.34.15
|
|
157
|
+
`);
|
|
158
|
+
await addAppToAppCollection(appCollectionId, app1.id);
|
|
159
|
+
await addAppToAppCollection(appCollectionId, app2.id);
|
|
160
|
+
await page.goto(`/en/collections/${appCollectionId}`);
|
|
142
161
|
await page.getByRole('button', { name: 'Edit' }).click();
|
|
143
162
|
await page
|
|
144
163
|
.locator('div', {
|
|
145
|
-
has: page.getByRole('link', { name:
|
|
146
|
-
hasNot: page.getByRole('link', { name:
|
|
164
|
+
has: page.getByRole('link', { name: app1.definition.name }),
|
|
165
|
+
hasNot: page.getByRole('link', { name: app2.definition.name }),
|
|
147
166
|
})
|
|
148
|
-
.
|
|
167
|
+
.getByTitle('Pin app to top of collection')
|
|
149
168
|
.click();
|
|
150
169
|
await page.getByRole('button', { name: 'Stop editing' }).click();
|
|
151
|
-
|
|
152
|
-
await expect(page.getByRole('main')).toHaveScreenshot({
|
|
153
|
-
timeout: 5000,
|
|
170
|
+
await expect(page.getByRole('main')).toHaveScreenshot('collection-apps-with-pinned.png', {
|
|
154
171
|
maxDiffPixelRatio: 0.02,
|
|
172
|
+
mask: [
|
|
173
|
+
page.getByRole('heading', { name: app1.definition.name }),
|
|
174
|
+
page.getByRole('heading', { name: app2.definition.name }),
|
|
175
|
+
page.getByRole('heading', { name: organizationId }),
|
|
176
|
+
],
|
|
155
177
|
});
|
|
178
|
+
await deleteApp(app1.id);
|
|
179
|
+
await deleteApp(app2.id);
|
|
156
180
|
});
|
|
157
181
|
});
|
|
158
182
|
//# sourceMappingURL=app-collections.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { expect, test } from '../../index.js';
|
|
3
|
+
let appId;
|
|
4
|
+
test.describe('Individual app', () => {
|
|
5
|
+
test.beforeAll(async ({ createApp, createOrganization, randomTestId }) => {
|
|
6
|
+
const { yaml } = JSON.parse(await readFile('mock-data/test-app-details.json', 'utf8'));
|
|
7
|
+
const organizationId = (await createOrganization({ id: randomTestId() })).id;
|
|
8
|
+
appId = (await createApp(organizationId, yaml)).id;
|
|
9
|
+
});
|
|
10
|
+
test.afterAll(async ({ deleteApp }) => {
|
|
11
|
+
await deleteApp(appId);
|
|
12
|
+
});
|
|
13
|
+
test.beforeEach(async ({ page }) => {
|
|
14
|
+
await page.goto(`/en/apps/${appId}`);
|
|
15
|
+
});
|
|
16
|
+
test('should prompt when user has unsaved changes', async ({ clickAppSideMenuItem, page }) => {
|
|
17
|
+
await clickAppSideMenuItem('Editor');
|
|
18
|
+
page.on('dialog', (alert) => {
|
|
19
|
+
expect(alert.type()).toBe('confirm');
|
|
20
|
+
expect(alert.message()).toBe('You have unsaved changes. Do you wish to continue?');
|
|
21
|
+
alert.dismiss();
|
|
22
|
+
});
|
|
23
|
+
await expect(page.getByRole('button', { name: 'Publish' })).toBeDisabled();
|
|
24
|
+
const codeEditor = page.getByRole('code');
|
|
25
|
+
await codeEditor.getByText('name: test app').click();
|
|
26
|
+
await codeEditor.getByText('name: test app').press('End');
|
|
27
|
+
await codeEditor.getByText('name: test app').press('Backspace');
|
|
28
|
+
await codeEditor.getByText('name: test ap').press('p');
|
|
29
|
+
await expect(page.getByRole('button', { name: 'Publish' })).toBeEnabled();
|
|
30
|
+
await page.getByRole('link', { name: 'Details' }).click();
|
|
31
|
+
await expect(page.getByText('Clone App')).toBeHidden();
|
|
32
|
+
});
|
|
33
|
+
test('should not prompt when user has saved their changes', async ({ clickAppSideMenuItem, page, }) => {
|
|
34
|
+
await clickAppSideMenuItem('Editor');
|
|
35
|
+
page.on('dialog', (alert) => {
|
|
36
|
+
// If a dialog comes up at all, the test should fail
|
|
37
|
+
expect(alert).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
await expect(page.getByRole('button', { name: 'Publish' })).toBeDisabled();
|
|
40
|
+
const codeEditor = page.getByRole('code');
|
|
41
|
+
await codeEditor.getByText('name: test app').click();
|
|
42
|
+
await codeEditor.getByText('name: test app').press('End');
|
|
43
|
+
await codeEditor.getByText('name: test app').press('Backspace');
|
|
44
|
+
await codeEditor.getByText('name: test ap').press('p');
|
|
45
|
+
await page.getByRole('button', { name: 'Publish' }).click();
|
|
46
|
+
await expect(page.getByText('Successfully updated app definition')).toBeVisible();
|
|
47
|
+
await page.getByRole('link', { name: 'Details' }).click();
|
|
48
|
+
await expect(page.getByText('Clone App')).toBeVisible();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=individual-app.spec.js.map
|