@appsemble/e2e 0.34.21 → 0.35.3-test.0

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.
Files changed (54) hide show
  1. package/README.md +3 -3
  2. package/fixtures/expect/training/index.js +4 -1
  3. package/fixtures/test/app/index.d.ts +24 -2
  4. package/fixtures/test/app/index.js +40 -1
  5. package/fixtures/test/app-collection/index.d.ts +14 -7
  6. package/fixtures/test/app-collection/index.js +46 -32
  7. package/fixtures/test/index.d.ts +1 -1
  8. package/fixtures/test/index.js +2 -1
  9. package/fixtures/test/live-app/index.d.ts +4 -2
  10. package/fixtures/test/live-app/index.js +11 -5
  11. package/fixtures/test/organization/index.d.ts +26 -0
  12. package/fixtures/test/organization/index.js +35 -0
  13. package/fixtures/test/studio/index.d.ts +32 -0
  14. package/fixtures/test/studio/index.js +26 -1
  15. package/fixtures/test/test-utils/index.d.ts +5 -1
  16. package/fixtures/test/test-utils/index.js +15 -1
  17. package/package.json +4 -4
  18. package/playwright.config.js +21 -1
  19. package/tests/demo-apps/empty.spec.js +26 -0
  20. package/tests/{template-apps → demo-apps}/holidays.spec.js +13 -1
  21. package/tests/{template-apps → demo-apps}/notes.spec.js +12 -3
  22. package/tests/{template-apps → demo-apps}/person.spec.js +15 -1
  23. package/tests/static-ui/apps.spec.js +37 -0
  24. package/tests/static-ui/blocks.spec.js +35 -0
  25. package/tests/static-ui/individual-app.spec.js +69 -0
  26. package/tests/static-ui/organizations.spec.js +41 -0
  27. package/tests/static-ui/training.spec.js +39 -0
  28. package/tests/studio/app-collections/app-collections.spec.js +127 -103
  29. package/tests/studio/individual-app.spec.d.ts +1 -0
  30. package/tests/studio/individual-app.spec.js +51 -0
  31. package/tests/studio/organizations.spec.js +31 -24
  32. package/tests/studio/payments/subscription-flow.spec.d.ts +1 -0
  33. package/tests/studio/payments/subscription-flow.spec.js +68 -0
  34. package/tests/studio/trainings.spec.d.ts +1 -0
  35. package/tests/studio/trainings.spec.js +105 -0
  36. package/tests/studio/apps/app.spec.js +0 -79
  37. package/tests/studio/apps.spec.js +0 -30
  38. package/tests/studio/blocks.spec.js +0 -20
  39. package/tests/studio/trainings/constants.d.ts +0 -4
  40. package/tests/studio/trainings/constants.js +0 -5
  41. package/tests/studio/trainings/logged-in.spec.js +0 -93
  42. package/tests/studio/trainings/logged-out.spec.js +0 -42
  43. package/tests/template-apps/empty.spec.js +0 -14
  44. /package/tests/{template-apps → demo-apps}/empty.spec.d.ts +0 -0
  45. /package/tests/{template-apps → demo-apps}/holidays.spec.d.ts +0 -0
  46. /package/tests/{template-apps → demo-apps}/notes.spec.d.ts +0 -0
  47. /package/tests/{template-apps → demo-apps}/person.spec.d.ts +0 -0
  48. /package/tests/{studio → static-ui}/apps.spec.d.ts +0 -0
  49. /package/tests/{studio → static-ui}/blocks.spec.d.ts +0 -0
  50. /package/tests/{studio → static-ui}/docs.spec.d.ts +0 -0
  51. /package/tests/{studio → static-ui}/docs.spec.js +0 -0
  52. /package/tests/{studio/apps/app.spec.d.ts → static-ui/individual-app.spec.d.ts} +0 -0
  53. /package/tests/{studio/trainings/logged-in.spec.d.ts → static-ui/organizations.spec.d.ts} +0 -0
  54. /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 ({ getAppId, giveAppConsent }) => {
4
- const appId = await getAppId('notes');
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('notes');
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('person');
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
- const organizationId = 'appsemble';
2
+ let organizationId;
3
3
  let appCollectionName;
4
+ let appCollectionId;
4
5
  test.describe('App Collections', () => {
5
- test.beforeEach(async ({ page }) => {
6
- await page.goto(`/en/organizations/${organizationId}/collections`);
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 (appCollectionName !== undefined) {
10
- await deleteAppCollection(organizationId, appCollectionName);
13
+ if (appCollectionId !== undefined) {
14
+ await deleteAppCollection(appCollectionId);
15
+ appCollectionId = undefined;
11
16
  }
12
17
  });
13
- test('should create an app collection', async ({ browserName, createAppCollection, page }) => {
14
- appCollectionName = `Create test ${browserName.slice(0, 8)}`;
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
- await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum\n\n**text in bold**\n\n![side image](/test-assets/side.png)', 'mock-data/retail.png', 'mock-data/tux.png', false);
19
- await page.getByRole('link', { name: appCollectionName }).click();
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![side image](/test-assets/side.png)');
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
- // TODO: Make sure this doesn't die in CI
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 create a private app collection', async ({ browser, browserName, createAppCollection, page, }) => {
34
- appCollectionName = `Private test ${browserName.slice(0, 8)}`;
35
- await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', true);
36
- await page.goto('/en/collections');
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('/en/collections');
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 ({ browserName, createAppCollection, page }) => {
51
- appCollectionName = `Edit test ${browserName.slice(0, 8)}`;
52
- await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
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: /Apps$/ }).click();
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 ({ browserName, createAppCollection, page }) => {
68
- appCollectionName = `Delete test ${browserName.slice(0, 8)}`;
69
- await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
70
- await page.getByRole('link', { name: `${appCollectionName} John Doe` }).click();
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.getByRole('heading', { name: 'App collections' }).waitFor({ state: 'visible' });
75
- await expect(page.getByRole('link', { name: appCollectionName })).toBeHidden({
76
- timeout: 5000,
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 ({ browserName, createAppCollection, page, }) => {
82
- appCollectionName = `Add app test ${browserName.slice(0, 8)}`;
83
- await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
84
- await page.getByRole('link', { name: 'App store' }).click();
85
- await page.getByRole('link', { name: 'Empty' }).first().click();
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.locator('#collectionId').selectOption({ label: appCollectionName });
89
- await page
90
- .locator('.modal-card-foot')
91
- .getByRole('button', { name: 'Add to collection' })
92
- .click();
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 ({ browserName, createAppCollection, page, }) => {
98
- appCollectionName = `Rm app test ${browserName.slice(0, 8)}`;
99
- await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
100
- await page.getByRole('link', { name: 'App store' }).click();
101
- await page.getByRole('link', { name: 'Empty' }).first().click();
102
- await page.click('#app-options-menu');
103
- await page.getByRole('button', { name: 'Add to collection' }).click();
104
- await page.locator('#collectionId').selectOption({ label: appCollectionName });
105
- await page
106
- .locator('.modal-card-foot')
107
- .getByRole('button', { name: 'Add to collection' })
108
- .click();
109
- await page.goto('/en/collections');
110
- await page.getByRole('link', { name: appCollectionName }).click();
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: 'Empty' }) })
114
- .locator('button[title="Delete app from collection"]')
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: 'Empty' })).toBeHidden();
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 ({ browserName, createAppCollection, page, }) => {
120
- appCollectionName = `Pin app test ${browserName.slice(0, 8)}`;
121
- await createAppCollection(organizationId, appCollectionName, 'John Doe', 'Lorem ipsum', 'mock-data/retail.png', 'mock-data/tux.png', false);
122
- await page.getByRole('link', { name: 'App store' }).click();
123
- await page.getByRole('link', { name: 'Empty' }).first().click();
124
- await page.click('#app-options-menu');
125
- await page.getByRole('button', { name: 'Add to collection' }).click();
126
- await page.locator('#collectionId').selectOption({ label: appCollectionName });
127
- await page
128
- .locator('.modal-card-foot')
129
- .getByRole('button', { name: 'Add to collection' })
130
- .click();
131
- await page.goBack();
132
- await page.getByRole('link', { name: 'Person' }).first().click();
133
- await page.click('#app-options-menu');
134
- await page.getByRole('button', { name: 'Add to collection' }).click();
135
- await page.locator('#collectionId').selectOption({ label: appCollectionName });
136
- await page
137
- .locator('.modal-card-foot')
138
- .getByRole('button', { name: 'Add to collection' })
139
- .click();
140
- await page.goto('/en/collections');
141
- await page.getByRole('link', { name: appCollectionName }).click();
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: 'Person' }),
146
- hasNot: page.getByRole('link', { name: 'Empty' }),
164
+ has: page.getByRole('link', { name: app1.definition.name }),
165
+ hasNot: page.getByRole('link', { name: app2.definition.name }),
147
166
  })
148
- .locator('button[title="Pin app to top of collection"]')
167
+ .getByTitle('Pin app to top of collection')
149
168
  .click();
150
169
  await page.getByRole('button', { name: 'Stop editing' }).click();
151
- // TODO make sure this doesn't die in CI either
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