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

QA-579 #3564

Merged
merged 8 commits into from
Aug 18, 2023
2 changes: 1 addition & 1 deletion tests/e2e_tests/docker-compose.e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ services:
- ${GUI_REPOSITORY}/junit:/e2e/junit
- ${GUI_REPOSITORY}/logs:/root/.npm/_logs
- ${GUI_REPOSITORY}/screenshots:/e2e/test-results
- ${GUI_REPOSITORY}/tests/e2e_tests/config.ts:/e2e/config.ts
- ${GUI_REPOSITORY}/tests/e2e_tests/dockerClient:/e2e/dockerClient
- ${GUI_REPOSITORY}/tests/e2e_tests/fixtures:/e2e/fixtures
- ${GUI_REPOSITORY}/tests/e2e_tests/integration:/e2e/integration
- ${GUI_REPOSITORY}/tests/e2e_tests/package.json:/e2e/package.json
- ${GUI_REPOSITORY}/tests/e2e_tests/playwright.config.ts:/e2e/playwright.config.ts
- ${GUI_REPOSITORY}/tests/e2e_tests/utils:/e2e/utils
- ${GUI_REPOSITORY}/videos:/e2e/videos

Expand Down
4 changes: 2 additions & 2 deletions tests/e2e_tests/integration/00-setup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ test.describe('Test setup', () => {

await page.click(`button:has-text('Sign up')`);
await page.waitForSelector(`button:has-text('Complete')`);
await page.fill('[id=name]', 'CI test corp');
await page.check('[id=tos]');
await page.getByLabel(/organization name/i).fill('CI test corp');
await page.getByLabel(/terms of service/i).check();
const frameHandle = await page.waitForSelector('iframe[title="reCAPTCHA"]');
await page.waitForTimeout(300);
const recaptchaFrame = await frameHandle.contentFrame();
Expand Down
99 changes: 44 additions & 55 deletions tests/e2e_tests/integration/01-login.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
test('Logs in using UI', async ({ context, page, password, username }) => {
console.log(`logging in user with username: ${username} and password: ${password}`);
// enter valid username and password
await page.waitForSelector(selectors.email);
await page.click(selectors.email);
await page.fill(selectors.email, username);
await page.waitForSelector(selectors.password);
await page.focus(selectors.password);
await page.fill(selectors.password, password);
await page.click(`:is(button:has-text('Log in'))`);
const emailInput = await page.getByPlaceholder(/email/i);
await emailInput.click();
await emailInput.fill(username);
const passwordInput = await page.getByLabel(/password/i);
await passwordInput.click();
await passwordInput.fill(password);
await page.getByRole('button', { name: /log in/i }).click();
// confirm we have logged in successfully
await page.waitForSelector(selectors.loggedInText);
await page.evaluate(() => localStorage.setItem(`onboardingComplete`, 'true'));
Expand All @@ -49,20 +49,13 @@
await page.waitForSelector(selectors.loggedInText);
// now we can log out
await page.click('.header-dropdown', { force: true });
await page.click(`text=/Log out/i`, { force: true });
await page.getByRole('menuitem', { name: /log out/i }).click();
await page.waitForSelector('text=/log in/i', { timeout: timeouts.tenSeconds });
});

test('fails to access unknown resource', async ({ baseUrl, page }) => {
await page.goto(`${baseUrl}ui/`);
const request = await axios({
url: `${baseUrl}/users`,
method: 'GET',
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});

const request = await axios.get(`${baseUrl}/users`, { httpsAgent: new https.Agent({ rejectUnauthorized: false }) });
Dismissed Show dismissed Hide dismissed
expect(request.status).toEqual(200);
const loginVisible = await page.isVisible(`:is(button:has-text('Log in'))`);
expect(loginVisible).toBeTruthy();
Expand All @@ -72,13 +65,13 @@
console.log(`logging in user with username: ${username} and password: lewrongpassword`);
await page.goto(`${baseUrl}ui/`);
// enter valid username and invalid password
await page.waitForSelector(selectors.email);
await page.click(selectors.email);
await page.fill(selectors.email, username);
await page.waitForSelector(selectors.password);
await page.click(selectors.password);
await page.fill(selectors.password, 'lewrongpassword');
await page.click(`:is(button:has-text('Log in'))`);
const emailInput = await page.getByPlaceholder(/email/i);
await emailInput.click();
await emailInput.fill(username);
const passwordInput = await page.getByLabel(/password/i);
await passwordInput.click();
await passwordInput.fill('lewrongpassword');
await page.getByRole('button', { name: /log in/i }).click();

// still on /login page plus an error is displayed
const loginVisible = await page.isVisible(`:is(button:has-text('Log in'))`);
Expand All @@ -97,39 +90,35 @@
});
});

test.describe('stays logged in across sessions, after browser restart if selected', () => {
test.beforeEach(async ({ baseUrl, page }) => {
await page.goto(`${baseUrl}ui/`);
});

test('pt1', async ({ context, password, page, username }) => {
console.log(`logging in user with username: ${username} and password: ${password}`);
// enter valid username and password
await page.waitForSelector(selectors.email);
await page.click(selectors.email);
await page.fill(selectors.email, username);
await page.waitForSelector(selectors.password);
await page.click(selectors.password);
await page.fill(selectors.password, password);
await page.check('[type=checkbox]', { force: true });
await page.click(`:is(button:has-text('Log in'))`);
test('stays logged in across sessions, after browser restart if selected', async ({ baseUrl, browser, context, password, page, username }) => {
console.log(`logging in user with username: ${username} and password: ${password}`);
await page.goto(`${baseUrl}ui/`);
// enter valid username and password
const emailInput = await page.getByPlaceholder(/email/i);
await emailInput.click();
await emailInput.fill(username);
const passwordInput = await page.getByLabel(/password/i);
await passwordInput.click();
await passwordInput.fill(password);
const checkbox = await page.getByLabel(/stay logged in/i);
await checkbox.check();
await page.getByRole('button', { name: /log in/i }).click();

// confirm we have logged in successfully
await page.waitForSelector(selectors.loggedInText);
const loginVisible = await page.isVisible(`:is(button:has-text('Log in'))`);
expect(loginVisible).toBeFalsy();
const cookies = await context.cookies();
process.env.LOGIN_STORAGE = JSON.stringify(cookies);
});
// confirm we have logged in successfully
await page.waitForSelector(selectors.loggedInText);
let loginVisible = await page.getByRole('button', { name: /log in/i }).isVisible();
expect(loginVisible).toBeFalsy();
await page.getByText('Help & support').click();
const cookies = await context.cookies();
await context.storageState({ path: 'storage.json' });

test('pt2', async ({ baseUrl, context }) => {
const cookies = JSON.parse(process.env.LOGIN_STORAGE);
await context.addCookies(cookies);
const page = await context.newPage();
await page.goto(`${baseUrl}ui/`);
const loginVisible = await page.isVisible(`:is(button:has-text('Log in'))`);
await context.storageState({ path: 'storage.json' });
expect(loginVisible).toBeFalsy();
});
const differentContext = await browser.newContext();
await differentContext.addCookies(cookies);
const differentPage = await differentContext.newPage();
await differentPage.goto(`${baseUrl}ui/`);
// page.reload();
loginVisible = await differentPage.getByRole('button', { name: /log in/i }).isVisible();
expect(loginVisible).toBeFalsy();
expect(await differentPage.getByText('Getting started').isVisible()).toBeFalsy();
});
});
29 changes: 17 additions & 12 deletions tests/e2e_tests/integration/03-files.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import axios from 'axios';
import dayjs from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween.js';
import * as fs from 'fs';
import https from 'https';
import md5 from 'md5';
import https from 'https';

import test, { expect } from '../fixtures/fixtures';
import { selectors, timeouts } from '../utils/constants';
import axios from 'axios';

dayjs.extend(isBetween);

Expand All @@ -31,7 +31,7 @@
await page.click(`.leftNav :text('Releases')`);
// create an artifact to download first
await page.click(`button:has-text('Upload')`);
await page.setInputFiles('.MuiDialog-paper .dropzone input', `fixtures/${fileName}`);
await page.locator('.MuiDialog-paper .dropzone input').setInputFiles(`fixtures/${fileName}`);
await page.click(`.MuiDialog-paper button:has-text('Upload')`);
// give some extra time for the upload
await page.waitForTimeout(timeouts.fiveSeconds);
Expand All @@ -55,14 +55,19 @@
await page.click(`.leftNav :text('Releases')`);
await page.click(`text=/mender-demo-artifact/i`);
await page.click('.expandButton');
await page.waitForSelector(`text=Download Artifact`, { timeout: timeouts.default });
expect(await page.isVisible(`text=Download Artifact`)).toBeTruthy();
// unfortunately the firefox integration gets flaky with the download attribute set + the chrome + webkit integrations time out
// waiting for the download event almost every time => work around by getting the file
const locator = await page.locator('text=Download Artifact');
const downloadUrl = await locator.getAttribute('href');
const download = await axios.get(downloadUrl, { httpsAgent: new https.Agent({ rejectUnauthorized: false }), responseType: 'arraybuffer' });
const newFile = download.data;
const downloadButton = await page.getByText(/download artifact/i);
expect(await downloadButton.isVisible()).toBeTruthy();
const [download] = await Promise.all([page.waitForEvent('download'), downloadButton.click()]);
let downloadTargetPath = await download.path();
const downloadError = await download.failure();
if (downloadError) {
const downloadUrl = download.url();
const response = await axios.get(downloadUrl, { httpsAgent: new https.Agent({ rejectUnauthorized: false }), responseType: 'arraybuffer' });
Dismissed Show dismissed Hide dismissed
const fileData = Buffer.from(response.data, 'binary');
downloadTargetPath = `./${download.suggestedFilename()}`;
fs.writeFileSync(downloadTargetPath, fileData);
}
const newFile = await fs.readFileSync(downloadTargetPath);
const testFile = await fs.readFileSync(`fixtures/${fileName}`);
expect(md5(newFile)).toEqual(md5(testFile));
});
Expand All @@ -77,7 +82,7 @@
await page.waitForSelector('text=/file transfer/i', { timeout: timeouts.tenSeconds });
await page.click(`css=.expandedDevice >> text=file transfer`);
await page.waitForSelector(`text=Connection with the device established`, { timeout: timeouts.tenSeconds });
await page.setInputFiles('.MuiDialog-paper .dropzone input', `fixtures/${fileName}`);
await page.locator('.dropzone input').setInputFiles(`fixtures/${fileName}`);
await page.click(selectors.placeholderExample, { clickCount: 3 });
await page.type(selectors.placeholderExample, `/tmp/${fileName}`);
await page.click(`button:text("Upload"):below(:text("Destination directory"))`);
Expand Down
4 changes: 1 addition & 3 deletions tests/e2e_tests/integration/04-deployments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ test.describe('Deployments', () => {
await releaseSelect.focus();
await releaseSelect.type('mender-demo');
await page.click(`#deployment-release-selection-listbox li`);
const clearButton = await page.getByRole('button', { name: 'Clear' });
await clearButton.click();
await page.getByRole('button', { name: 'Clear' }).click();
const textContent = await releaseSelect.textContent();
expect(textContent).toBeFalsy();
await releaseSelect.focus();
Expand All @@ -83,7 +82,6 @@ test.describe('Deployments', () => {
});

test('allows group deployments', async ({ loggedInPage: page }) => {
console.log(`allows group deployments`);
await page.click(`a:has-text('Deployments')`);
await page.click(`button:has-text('Create a deployment')`);

Expand Down
5 changes: 3 additions & 2 deletions tests/e2e_tests/integration/05-deviceDetails.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ test.describe('Device details', () => {
const searchField = await page.getByPlaceholder(/search devices/i);
await searchField.fill(demoDeviceName);
await page.waitForSelector('.deviceListItem');
expect(await page.locator(`:text("${demoDeviceName}"):below(:text("clear search"))`).isVisible()).toBeTruthy();
expect(await page.getByText('1-1 of 1').isVisible()).toBeTruthy();
const slideOut = await page.locator('.MuiPaper-root');
expect(await slideOut.locator(`:text("${demoDeviceName}"):below(:text("clear search"))`).isVisible()).toBeTruthy();
expect(await slideOut.getByText('1-1 of 1').isVisible()).toBeTruthy();
await page.click(`.deviceListItem`);
await page.waitForSelector('text=/device information/i');
expect(await page.getByText(/Authorization sets/i).isVisible()).toBeTruthy();
Expand Down
45 changes: 9 additions & 36 deletions tests/e2e_tests/integration/07-saml.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,54 +59,41 @@ test.describe('SAML Login via sso/id/login', () => {
});

// Setups the SAML/SSO login with samltest.id Identity Provider
test('Set up SAML', async ({ browserName, context, environment, baseUrl, page }) => {
// QA-579
test.skip(browserName === 'webkit');

test('Set up SAML', async ({ context, environment, baseUrl, page }) => {
test.skip(environment !== 'staging');
// allow a lot of time to enter metadata + then some to handle uploading the config to the external service
test.setTimeout(5 * timeouts.sixtySeconds + timeouts.fifteenSeconds);

const { data: metadata, status } = await axios({ url: samlSettings.idp_url, method: 'GET' });
expect(status).toBeGreaterThanOrEqual(200);
expect(status).toBeLessThan(300);

console.log(`IdP metadata len=${metadata.length} loaded and uploading`);
await page.goto(`${baseUrl}ui/settings/organization-and-billing`);

const isInitialized = await page.isVisible('text=Entity ID');
if (!isInitialized) {
// Check input[type="checkbox"]
await page.locator('input[type="checkbox"]').check();
// Click text=input with the text editor
await page.locator('text=input with the text editor').click();

// Click .view-lines
await page.locator('.view-lines').click();

console.log('typing metadata');
await page
.locator('[aria-label="Editor content\\;Press Alt\\+F1 for Accessibility Options\\."]')
.type(metadata.replace(/(?:\r\n|\r|\n)/g, ''), { delay: 0 });
const textfield = await page.locator('[aria-label="Editor content\\;Press Alt\\+F1 for Accessibility Options\\."]');
await textfield.type(metadata.replace(/(?:\r\n|\r|\n)/g, ''));
console.log('typing metadata done.');
// The screenshot saves the view of the typed metadata
await page.screenshot({ 'path': 'saml-edit-saving.png' });
// Click text=Save >> nth=1
await page.locator('text=Save').nth(1).click();
console.log('typing metadata done. waiting for Entity ID to be present on page.');
await page.waitForSelector('text=Entity ID');
}

await page.locator('text=View metadata in the text editor').click();
// Click text=Download file
const [download] = await Promise.all([page.waitForEvent('download'), page.locator('text=Download file').click()]);
const [download] = await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: /download file/i }).click()]);
const downloadTargetPath = await download.path();
expect(downloadTargetPath).toBeTruthy();
const dialog = await page.locator('text=SAML metadata >> .. >> ..');
await dialog.locator('data-testid=CloseIcon').click();

const storage = await context.storageState();
const jwt = storage['cookies'].find(cookie => cookie.name === 'JWT').value;
const jwt = storage.cookies.find(cookie => cookie.name === 'JWT').value;
const requestInfo = { method: 'GET', headers: { ...defaultHeaders, Authorization: `Bearer ${jwt}` }, httpsAgent };
const { data } = await axios({ ...requestInfo, url: `${baseUrl}api/management/v1/useradm/sso/idp/metadata` });
const metadataId = data[0].id;
Expand All @@ -127,24 +114,15 @@ test.describe('SAML Login via sso/id/login', () => {
fs.writeFileSync('fixtures/service_provider_metadata.xml', serviceProviderMetadata);

await page.goto('https://samltest.id/upload.php');
await page.waitForSelector('input[id=uploader]');
const handle = await page.$('input[id="uploader"]');
await handle.setInputFiles('fixtures/service_provider_metadata.xml');
// Click input:has-text("Upload")
await page.locator('input:has-text("Upload")').click();
await page.waitForSelector('text=Metadata Upload Form');
await page.locator('input[type="file"]').setInputFiles('fixtures/service_provider_metadata.xml');
await page.getByRole('button', { name: /upload/i }).click();
await expect(page).toHaveURL('https://samltest.id/upload.php');

console.log('uploaded file, making screen shot, after waiting 5s');
await page.waitForTimeout(timeouts.fiveSeconds);
// Let's save the image after the upload
await page.screenshot({ path: './test-results/saml-uploaded.png' });
await page.waitForSelector('text=We now trust you');
});

// Creates a user with login that matches Identity privder (samltest.id) user email
test('Creates a user without a password', async ({ environment, browserName, baseUrl, page }) => {
// QA-579
test.skip(browserName === 'webkit');

test.skip(environment !== 'staging');
await page.goto(`${baseUrl}ui/settings/user-management`);
const userExists = await page.isVisible(`text=${samlSettings.credentials[browserName].email}`);
Expand All @@ -168,10 +146,6 @@ test.describe('SAML Login via sso/id/login', () => {
// This test calls auth/sso/${id}/login, where id is the id of the identity provider
// and verifies that login is successful.
test('User can login via sso/login endpoint', async ({ environment, browserName, baseUrl, browser, loggedInPage }) => {
// QA-579
test.skip(browserName === 'firefox');
test.skip(browserName === 'webkit');

test.skip(environment !== 'staging');
test.setTimeout(2 * timeouts.fifteenSeconds);

Expand All @@ -188,7 +162,6 @@ test.describe('SAML Login via sso/id/login', () => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(loginUrl);
await page.waitForSelector(selectors.loggedInText);
// This screenshot saves the view right after the first redirection
await page.screenshot({ path: './test-results/saml-redirected.png' });

Expand Down
7 changes: 4 additions & 3 deletions tests/e2e_tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
"url": "git+https://github.com/mendersoftware/integration.git"
},
"scripts": {
"test": "npx playwright test --workers 1 --config=config.ts",
"test-ci": "mv mender-demo-artifact.mender fixtures/ && npx playwright test --workers 1 --config=config.ts",
"test-visual": "npx playwright test --ui --workers 1 --config=config.ts"
"test": "npx playwright test --workers 1",
"test-ci": "mv mender-demo-artifact.mender fixtures/ && npx playwright test --workers 1",
"test-visual": "PWDEBUG=1 npx playwright test --workers 1",
"test-visual-new": "npx playwright test --ui --workers 1"
},
"author": "Northern.tech AS",
"license": "Apache-2.0",
Expand Down
Loading
Loading