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

Playwright tests for ACLs #358

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion blocks/browse/browse.css
Original file line number Diff line number Diff line change
@@ -1 +1 @@

/* This file is intentionally empty. */
1 change: 1 addition & 0 deletions blocks/edit/edit.css
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/* This file is intentionally empty. */

2 changes: 2 additions & 0 deletions test/e2e/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ node_modules/
/playwright-report/
/blob-report/
/playwright/.cache/
.playwright/

21 changes: 18 additions & 3 deletions test/e2e/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,34 @@ module.exports = defineConfig({

/* Configure projects for major browsers */
projects: [
// Setup project
{ name: 'setup', testMatch: /.*\.setup\.js/ },

{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
use: {
...devices['Desktop Chrome'],
storageState: '.playwright/.auth/user.json',
},
dependencies: ['setup'],
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
use: {
...devices['Desktop Firefox'],
storageState: '.playwright/.auth/user.json',
},
dependencies: ['setup'],
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
use: {
...devices['Desktop Safari'],
storageState: '.playwright/.auth/user.json',
},
dependencies: ['setup'],
},

/* Test against mobile viewports. */
Expand Down
106 changes: 106 additions & 0 deletions test/e2e/tests/acl_browse.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { test, expect } from '@playwright/test';
import { getTestPageURL } from '../utils/page.js';

test('Read-only directory', async ({ page }) => {
const url = 'https://da.live/#/da-testautomation/acltest/testdocs/subdir';

await page.goto(url);
const newButton = page.getByRole('button', { name: 'New' });
await expect(newButton).toBeDisabled();

await expect(page.locator('a[href="/edit#/da-testautomation/acltest/testdocs/subdir/doc_onlyread"]')).toBeVisible();
await page.locator('a[href="/edit#/da-testautomation/acltest/testdocs/subdir/doc_onlyread"]').focus();

// Note this currently does not work on webkit as the checkbox isn't keyboard focusable there
await page.keyboard.press('Shift+Tab');
await page.keyboard.press(' ');
await page.waitForTimeout(500);

const tickbox = page.locator('da-list-item').filter({ hasText: 'doc_onlyread' }).locator('label');
await expect(tickbox).toBeChecked();

// There should not be a delete button
await expect(page.locator('button.delete-button').locator('visible=true')).toHaveCount(0);
});

test('Read-write directory', async ({ browser, page }, workerInfo) => {
const browseURL = 'https://da.live/#/da-testautomation/acltest/testdocs/subdir/subdir1';

const pageURL = getTestPageURL('acl-browse-edt', workerInfo, '/da-testautomation/acltest/testdocs/subdir/subdir1');
const pageName = pageURL.split('/').pop();

await page.goto(browseURL);
const newButton = page.getByRole('button', { name: 'New' });
await expect(newButton).toBeEnabled();
await newButton.click();
await page.locator('button:text("Document")').click();
await page.locator('input.da-actions-input').fill(pageName);

await page.locator('button:text("Create document")').click();
await expect(page.locator('div.ProseMirror')).toBeVisible();
await page.locator('div.ProseMirror').fill('test writable doc');
await page.waitForTimeout(3000);

const newPage = await browser.newPage();
await newPage.goto(pageURL);
// The following assertion has an extended timeout as it might cycle through the login screen
// before the document is visible. The login screen doesn't need any input though, it will just
// continue with the existing login
await expect(newPage.locator('div.ProseMirror')).toContainText('test writable doc', { timeout: 10000 });
newPage.close();

await page.goto(browseURL);
await expect(page.locator(`a[href="/edit#/da-testautomation/acltest/testdocs/subdir/subdir1/${pageName}"]`)).toBeVisible();
await page.locator(`a[href="/edit#/da-testautomation/acltest/testdocs/subdir/subdir1/${pageName}"]`).focus();

// Note this currently does not work on webkit as the checkbox isn't keyboard focusable there
await page.keyboard.press('Shift+Tab');
await page.keyboard.press(' ');
await page.waitForTimeout(500);

const tickbox = page.locator('da-list-item').filter({ hasText: pageName }).locator('label');
await expect(tickbox).toBeChecked();

// There are 2 delete buttons, one on the Browse panel and another on the Search one
// select the visible one.
await page.locator('button.delete-button').locator('visible=true').click();

await page.waitForTimeout(1000);
await expect(page.locator(`a[href="/edit#/da-testautomation/acltest/testdocs/subdir/subdir1/${pageName}"]`)).not.toBeVisible();
});

test('Readonly directory with writeable document', async ({ page }) => {
const browseURL = 'https://da.live/#/da-testautomation/acltest/testdocs/subdir/subdir2';
await page.goto(browseURL);

await expect(page.locator('a[href="/edit#/da-testautomation/acltest/testdocs/subdir/subdir2/doc_writeable"]')).toBeVisible();
await page.locator('a[href="/edit#/da-testautomation/acltest/testdocs/subdir/subdir2/doc_writeable"]').focus();

// Note this currently does not work on webkit as the checkbox isn't keyboard focusable there
await page.keyboard.press('Shift+Tab');
await page.keyboard.press(' ');
await page.waitForTimeout(500);

// Check that the expected delete button is there (but don't click it)
await expect(page.locator('button.delete-button').locator('visible=true')).toBeVisible();

// Open the document, this will open an new tab (aka 'popup')
const newTabPromise = page.waitForEvent('popup');
await page.locator('a[href="/edit#/da-testautomation/acltest/testdocs/subdir/subdir2/doc_writeable"]').click();
const newTab = await newTabPromise;

const editor = newTab.locator('div.ProseMirror');
await expect(editor).toContainText('This is doc_writeable');
await expect(editor).toHaveAttribute('contenteditable', 'true');
});
89 changes: 89 additions & 0 deletions test/e2e/tests/acl_doc.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { test, expect } from '@playwright/test';

test('Read-only document directly configured', async ({ page }, workerInfo) => {
const url = 'https://da.live/edit#/da-testautomation/acltest/testdocs/doc_readonly';

await page.goto(url);
const editor = page.locator('div.ProseMirror');
await expect(editor).toHaveText('This is doc_readonly');
await expect(editor, 'Should be readonly').toHaveAttribute('contenteditable', 'false');

await editor.pressSequentially('Hello');
await expect(editor, 'The text should not have been updated since its a readonly doc')
.toHaveText('This is doc_readonly');

// This last part of this test that obtains the ':before' part of the h1
// apparently only works on Chromium, so skip it for other browsers
if (workerInfo.project.name !== 'chromium') {
return;
}

// check the lock icon
const h1 = page.locator('h1');
const h1Before = await h1.evaluate((element) => window.getComputedStyle(element, ':before'));
expect(h1Before.backgroundImage).toContain('LockClosed');
});

test('Read-only document indirectly configured', async ({ page }, workerInfo) => {
const url = 'https://da.live/edit#/da-testautomation/acltest/testdocs/subdir/doc_onlyread';

await page.goto(url);
const editor = page.locator('div.ProseMirror');
await expect(editor).toHaveText('This is doc_onlyread');
await expect(editor, 'Should be readonly').toHaveAttribute('contenteditable', 'false');

await editor.pressSequentially('Hello');
await expect(editor, 'The text should not have been updated since its a readonly doc')
.toHaveText('This is doc_onlyread');

// This last part of this test that obtains the ':before' part of the h1
// apparently only works on Chromium, so skip it for other browsers
if (workerInfo.project.name !== 'chromium') {
return;
}

// check the lock icon
const h1 = page.locator('h1');
const h1Before = await h1.evaluate((element) => window.getComputedStyle(element, ':before'));
expect(h1Before.backgroundImage).toContain('LockClosed');
});

test('Read-write document', async ({ page }, workerInfo) => {
const url = 'https://da.live/edit#/da-testautomation/acltest/testdocs/doc_readwrite';

await page.goto(url);
const editor = page.locator('div.ProseMirror');
await expect(editor).toContainText('This is doc_readwrite');
await expect(editor, 'Should be editable').toHaveAttribute('contenteditable', 'true');

// This last part of this test that obtains the ':before' part of the h1
// apparently only works on Chromium, so skip it for other browsers
if (workerInfo.project.name !== 'chromium') {
return;
}

// check the lock icon
const h1 = page.locator('h1');
const h1Before = await h1.evaluate((element) => window.getComputedStyle(element, ':before'));
expect(h1Before.backgroundImage).not.toContain('LockClosed');
});

test('No access at all', async ({ page }) => {
const url = 'https://da.live/edit#/da-testautomation/acltest/testdocs/doc_noaccess';

await page.goto(url);

await expect(page.locator('h1')).toContainText('doc_noaccess');
await expect(page.locator('div.ProseMirror'), 'Nothing should be visible').toHaveCount(0);
});
50 changes: 50 additions & 0 deletions test/e2e/tests/auth.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { test as setup, expect } from '@playwright/test';
import path from 'path';

const authFile = path.join(__dirname, '../.playwright/.auth/user.json');

/*
The ACL tests require to be logged in, which is what this setup does.
It is assumed to be configured as follows, where the current est user is in IMS org
907136ED5D35CBF50A495CD4 and in its group DA-Test BUT NOT iN DA-Nonexist.

path groups actions
/acltest/testdocs/doc_readonly 907136ED5D35CBF50A495CD4read
/acltest/testdocs/doc_readwrite 907136ED5D35CBF50A495CD4/DA-Test write
/acltest/testdocs/subdir/+** 907136ED5D35CBF50A495CD4 read
/acltest/testdocs/doc_noaccess 907136ED5D35CBF50A495CD4/DA-Nonexist write
/acltest/testdocs/subdir/subdir2/** 907136ED5D35CBF50A495CD4 write
/acltest/testdocs/subdir/subdir2 907136ED5D35CBF50A495CD4 read
/acltest/testdocs/subdir/subdir1/+** 907136ED5D35CBF50A495CD4 write
/acltest/testdocs/subdir/subdir2./subdir3 907136ED5D35CBF50A495CD4 read
*/

// This is executed once to authenticate the user used during the tests.
setup('Set up authentication', async ({ page }) => {
const url = 'https://da.live';

await page.goto(url);
await page.getByRole('button', { name: 'Sign in' }).click();

// The IMS sign in page needs a bit of time to load
await page.waitForTimeout(1000);
await page.getByLabel('Email address').fill('[email protected]');
await page.getByRole('button', { name: 'Continue', exact: true }).click();
await page.getByLabel('Password', { exact: true }).fill(process.env.TEST_PASSWORD);
await page.getByLabel('Continue').click();
await page.getByLabel('Foundation Internal').click();
await expect(page.locator('a.nx-nav-brand')).toContainText('Document Authoring');

await page.context().storageState({ path: authFile });
});
23 changes: 17 additions & 6 deletions test/e2e/tests/collab.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,33 @@ import { test, expect } from '@playwright/test';
import { getTestPageURL } from '../utils/page.js';

test('Collab cursors in multiple editors', async ({ browser, page }, workerInfo) => {
// Open 2 editors on the same page and edit in both of them
// Open 2 editors on the same page and edit in both of them. One editor is logged in,
// the other isn't.
// Ensure that the edits are visible to both and that the collab cursors are there
// Also check that the cloud icon is visible for the collaborator

const pageURL = getTestPageURL('collab', workerInfo);

await page.goto(pageURL);
// Wait a little bit so that the collab awareness has caught up and knows that we are logged in as
// 'DA Testuser'
// TODO this should not be necessary
await page.waitForTimeout(1000);
await page.reload();
await page.waitForTimeout(1000);

await expect(page.locator('div.ProseMirror')).toBeVisible();
await page.locator('div.ProseMirror').fill('Entered by user 1');

// Right now there should not be any collab indicators yet
await expect(page.locator('div.collab-icon.collab-icon-user[data-popup-content="Anonymous"]')).not.toBeVisible();
await expect(page.locator('div.collab-icon.collab-icon-user[data-popup-content="DA Testuser"]')).not.toBeVisible();
await expect(page.locator('span.ProseMirror-yjs-cursor')).not.toBeVisible();

const page2 = await browser.newPage();
// Open a new browser page with an empty storage state. which means its not logged in and
// will have an anonymous user
const page2 = await browser.newPage({ storageState: {} });
await page2.goto(pageURL);

await expect(page2.locator('div.ProseMirror')).toBeVisible();
await expect(page2.locator('div.ProseMirror')).toContainText('Entered by user 1');

Expand All @@ -38,14 +49,14 @@ test('Collab cursors in multiple editors', async ({ browser, page }, workerInfo)
await page2.keyboard.type('From user 2');

// Check the little cloud icon for collaborators
await expect(page2.locator('div.collab-icon.collab-icon-user[data-popup-content="Anonymous"]')).toBeVisible();
await expect(page2.locator('div.collab-icon.collab-icon-user[data-popup-content="DA Testuser"]')).toBeVisible();

// Check the cursor for collaborator
await expect(page2.locator('span.ProseMirror-yjs-cursor')).toBeVisible();
await expect(page2.locator('span.ProseMirror-yjs-cursor')).toContainText('Anonymous');
await expect(page2.locator('span.ProseMirror-yjs-cursor')).toContainText('DA Testuser');
const text2 = await page2.locator('div.ProseMirror').innerText();
const text2Idx = text2.indexOf('From user 2Entered by user 1');
const cursor2Idx = text2.indexOf('Anonymous');
const cursor2Idx = text2.indexOf('DA Testuser');
expect(text2Idx).toBeGreaterThanOrEqual(0);
expect(cursor2Idx).toBeGreaterThanOrEqual(0);
expect(cursor2Idx).toBeGreaterThan(text2Idx);
Expand Down
8 changes: 4 additions & 4 deletions test/e2e/utils/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export function getQuery() {

const QUERY = getQuery();

function getTestURL(type, testIdentifier, workerInfo) {
function getTestURL(type, testIdentifier, workerInfo, dir = '/da-sites/da-status/tests') {
const dateStamp = Date.now().toString(36);
const pageName = `pw-${testIdentifier}-${dateStamp}-${workerInfo.project.name}`;
return `${ENV}/${type}${QUERY}#/da-sites/da-status/tests/${pageName}`;
return `${ENV}/${type}${QUERY}#${dir}/${pageName}`;
}

/**
Expand All @@ -34,8 +34,8 @@ function getTestURL(type, testIdentifier, workerInfo) {
* @param {object} workerInfo - workerInfo as passed in by Playwright
* @returns {string} The URL for the test page.
*/
export function getTestPageURL(testIdentifier, workerInfo) {
return getTestURL('edit', testIdentifier, workerInfo);
export function getTestPageURL(testIdentifier, workerInfo, dir) {
return getTestURL('edit', testIdentifier, workerInfo, dir);
}

/**
Expand Down
Loading