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

chore: add basic smoke test for mattermost with sso #65

Merged
merged 33 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9314370
add basic smoke test for mattermost with sso
zachariahmiller May 3, 2024
6a35f88
add new line
zachariahmiller May 3, 2024
8129bbb
fix name of task in upgrade test
zachariahmiller May 4, 2024
5b32aea
fix ci, better login handling
zachariahmiller May 7, 2024
6a27f13
Merge branch 'main' into playwright-test
zachariahmiller May 7, 2024
c54e633
remove tests beyond login
zachariahmiller May 7, 2024
37b901e
mitigate upgrade issue with sso
zachariahmiller May 7, 2024
cb9324d
Merge branch 'main' into playwright-test
zachariahmiller May 7, 2024
577219f
Merge branch 'main' into playwright-test
zachariahmiller May 10, 2024
a2c8668
fix
zachariahmiller May 10, 2024
14b4574
Merge branch 'main' into playwright-test
zachariahmiller May 10, 2024
914c85c
test sending a message
marshall007 May 14, 2024
56f4e15
fix: replaced hardcoded redirectUri (#73)
Racer159 May 13, 2024
4308c87
fix: pre-commit yaml lint config and postgres config error (#74)
corang May 13, 2024
92ef823
chore(main): release 9.7.2-uds.2 (#76)
github-actions[bot] May 13, 2024
a41afcb
update tests/mattermost-smoketest.spec.ts
zachariahmiller May 14, 2024
cbb1384
make UI tests more robust
marshall007 May 15, 2024
59bc494
simplify test names
marshall007 May 15, 2024
a8f6297
move package.json to tests/
marshall007 May 15, 2024
4368f59
test file uploads
marshall007 May 15, 2024
a7f54dc
fix dismissing onboarding overlays
marshall007 May 16, 2024
54cf60f
fix draft overlay
marshall007 May 16, 2024
310bcb0
await overlay dismissal
marshall007 May 16, 2024
412560a
overlay/demo checks
marshall007 May 16, 2024
ee0b723
switch to API testing
marshall007 May 17, 2024
0a192f7
feat: add oidc option for sso (#77)
corang May 16, 2024
d336808
poll for channel list
marshall007 May 20, 2024
3516071
disable download test
marshall007 May 20, 2024
f91e4c5
fix async test setup
marshall007 May 20, 2024
d39d00d
wait until network idle?
marshall007 May 20, 2024
c20716e
Merge branch 'main' into playwright-test
Racer159 May 20, 2024
29f770e
Apply suggestions from code review
marshall007 May 21, 2024
68cff3b
remove download test
marshall007 May 21, 2024
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
7 changes: 7 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,10 @@ jobs:
uses: defenseunicorns/uds-common/.github/actions/save-logs@b2e8b25930c953ef893e7c787fe350f0d8679ee2 # v0.4.2
with:
suffix: ${{ matrix.type }}-${{ matrix.flavor }}-${{ github.run_id }}-${{ github.run_attempt }}

- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: always()
with:
name: playwright-report-${{ matrix.type }}-${{ matrix.flavor }}-${{ github.run_id }}-${{ github.run_attempt }}
path: tests/.playwright/reports/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ terraform.tfstate.backup

# SOPS stuff that should never be committed to the repo
secret-sops-gpg.yaml

# Tests
node_modules/
.playwright/
6 changes: 6 additions & 0 deletions tasks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,22 @@ tasks:
- task: create-mm-test-bundle
- task: setup:k3d-test-cluster
- task: deploy:test-bundle
- task: setup:create-doug-user
- task: test:health-check
- task: test:ingress
- task: test:ui

- name: test-upgrade
description: Test an upgrade from the latest released package to the current branch
actions:
- task: create-mm-latest-release-bundle
- task: setup:k3d-test-cluster
- task: deploy:test-bundle
- task: setup:create-doug-user
- task: create-mm-test-bundle
- task: deploy:test-bundle
- cmd: ./uds zarf tools kubectl delete pods --all --namespace mattermost
description: "Workaround - restart the pods so the sso configuration will take effect"
- task: test:health-check
- task: test:ingress
- task: test:ui
10 changes: 10 additions & 0 deletions tasks/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ tasks:
protocol: https
address: chat.uds.dev
code: 200

- name: ui
description: Mattermost UI Checks
actions:
- cmd: npm ci
dir: tests
- cmd: npx playwright install --with-deps
dir: tests
- cmd: npx playwright test
dir: tests
marshall007 marked this conversation as resolved.
Show resolved Hide resolved
40 changes: 40 additions & 0 deletions tests/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { test as setup, expect } from '@playwright/test';
import { authFile } from './playwright.config';

setup('authenticate', async ({ page, context, baseURL }) => {
await page.goto('/login');

await page.getByRole("link", { name: "View in Browser" }).click();
await page.getByRole("link", { name: "Gitlab Icon GitLab" }).click();
await page.getByLabel("Username or email").fill("doug");
await page.getByLabel("Password").fill("unicorn123!@#");
await page.getByRole("button", { name: "Log In" }).click();

// ensure auth cookies were set
const cookies = await context.cookies();
const keycloakCookie = cookies.find(
(cookie) => cookie.name === "KEYCLOAK_SESSION",
);

expect(keycloakCookie).toBeDefined();
expect(keycloakCookie?.value).not.toBe("");
expect(keycloakCookie?.domain).toContain("sso.uds.dev");
marshall007 marked this conversation as resolved.
Show resolved Hide resolved

await page.context().storageState({ path: authFile });

await page.waitForURL(url =>
url.pathname === '/' ||
url.pathname === '/preparing-workspace' ||
url.pathname === '/unicorns/channels/town-square'
, { waitUntil: 'networkidle' });

// one-time workspace setup (when login redirects to "/preparing-workspace")
if (page.url().endsWith('/preparing-workspace')) {
const orgInput = page.getByPlaceholder("Organization name");
await orgInput.isVisible();
await orgInput.fill("Unicorns");
await page.getByTestId("continue").click();
await page.getByRole("button", { name: "Skip" }).click();
await page.getByRole("button", { name: "Finish setup" }).click();
};
})
118 changes: 118 additions & 0 deletions tests/mattermost.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { test, expect, request, APIRequestContext, Page } from "@playwright/test";
import consumers from 'stream/consumers';

type Channel = { id: string; name: string; display_name: string; };

let apiCtx: APIRequestContext;
let channel: Channel;

test.beforeEach(async ({ context, baseURL }) => {
const cookies = await context.cookies();
const token = cookies.find(c => c.name === 'MMAUTHTOKEN');

expect(token?.value).toBeDefined();

apiCtx = await request.newContext({
baseURL,
extraHTTPHeaders: {
'Accept': 'application/json',
'Authorization': `Bearer ${token?.value}`,
'X-Requested-With': 'XMLHttpRequest', // without this you get "Invalid or expired session, please login again."
}
});

// poll channel list in case default channels haven't been created yey
marshall007 marked this conversation as resolved.
Show resolved Hide resolved
await expect(async () => {
const channels = await apiCtx.get('/api/v4/channels')
.then(res => res.json());

expect(channels.length).toBeGreaterThanOrEqual(1);

channel = channels.find((c: Channel) => c.display_name === 'Town Square');
expect(channel).toBeDefined();
expect(channel.display_name).toBe('Town Square');
}).toPass({
timeout: 60_000,
});
});

function randomMessage(extra: string = "") {
return `hello universe ${Math.floor((Math.random() * 1000))}-C3!` + extra;
}

async function createPost(page: Page, data: { channel_id: string, message: string; file_ids?: string[] }) {
const req = await apiCtx.post('/api/v4/posts', {
data,
});

expect(req).toBeOK();

const post = await req.json();

expect(post.id).toBeDefined();
expect(post.message).toBe(data.message);

return post;
}

test("send a message", async ({ page }) => {
const post = await createPost(page, {
channel_id: channel.id,
message: randomMessage(),
});

await page.goto('/unicorns/channels/town-square');

const el = page.locator(`#post_${post.id}`);
await expect(el).toContainText(post.message);
});

test("send a message with attachment", async ({ page }) => {
const file = {
name: 'README.md',
mimeType: 'text/plain',
buffer: Buffer.from('# My Cool Project'),
};

const upload = await apiCtx.fetch('/api/v4/files', {
method: 'POST',
multipart: {
channel_id: channel.id,
files: file,
},
});

expect(upload).toBeOK();

const res = await upload.json();

expect(res.file_infos.length).toBe(1);
expect(res.file_infos[0].name).toBe(file.name);

const message = randomMessage('\ncheckout this attachment...');
const post = await createPost(page, {
channel_id: channel.id,
message,
file_ids: [ res.file_infos[0].id ],
});

await page.goto('/unicorns/channels/town-square');

const el = page.locator(`#post_${post.id}`);
await expect(el).toContainText(post.message);

// TODO @marshall007: I can't get this to work because UI feature tour stuff still gets in the way

// const downloadWait = page.waitForEvent('download');

// await expect(async () => {
// await el.getByRole('link', { name: 'download' }).click({ force: true });

// const download = await downloadWait;
// expect(download.suggestedFilename()).toBe(file.name);

// const data = await consumers.text(await download.createReadStream())

// expect(data).toBe(file.buffer.toString('utf8'));
// }, 'download completed').toPass();
marshall007 marked this conversation as resolved.
Show resolved Hide resolved
});
104 changes: 104 additions & 0 deletions tests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "uds-package-mattermost",
"license": "Apache-2.0",
"devDependencies": {
"@playwright/test": "^1.43.1",
"@types/node": "^20.12.12",
"typescript": "^5.4.5"
}
}
43 changes: 43 additions & 0 deletions tests/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { defineConfig, devices } from '@playwright/test';

export const playwrightDir = '.playwright';
export const authFile = `${playwrightDir}/auth/user.json`;

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
fullyParallel: true,
forbidOnly: !!process.env.CI, // fail CI if you accidently leave `test.only` in source
retries: process.env.CI ? 1 : 0,
workers: 1,
reporter: [
// Reporter to use. See https://playwright.dev/docs/test-reporters
['html', { outputFolder: `${playwrightDir}/reports`, open: 'never' }],
['json', { outputFile: `${playwrightDir}/reports/test-results.json`, open: 'never' }],
['list']
],

outputDir: `${playwrightDir}/output`,

use: {
baseURL: process.env.BASE_URL || 'https://chat.uds.dev', // for `await page.goto('/')` etc
trace: 'on-first-retry', // collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer
},

projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ }, // authentication

...[
'Desktop Chrome',
'Desktop Firefox',
].map((p) => ({
name: devices[p].defaultBrowserType,
dependencies: ['setup'],
use: {
...devices[p],
storageState: authFile,
},
})),
],
});
10 changes: 10 additions & 0 deletions tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "commonjs", /* Specify what module code is generated. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}