diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 979795d4..4abf2dca 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -105,6 +105,8 @@ jobs:
LOGIN_EMAIL: user@automatisch.io
LOGIN_PASSWORD: sample
BASE_URL: http://localhost:3000
+ GITHUB_CLIENT_ID: 1c0417daf898adfbd99a
+ GITHUB_CLIENT_SECRET: 3328fa814dd582ccd03dbe785cfd683fb8da92b3
run: yarn test
- uses: actions/upload-artifact@v3
if: always()
diff --git a/packages/e2e-tests/fixtures/applications-modal.js b/packages/e2e-tests/fixtures/applications-modal.js
new file mode 100644
index 00000000..3a0d1de8
--- /dev/null
+++ b/packages/e2e-tests/fixtures/applications-modal.js
@@ -0,0 +1,34 @@
+const { GithubPage } = require('./apps/github/github-page');
+const { BasePage } = require('./base-page');
+
+export class ApplicationsModal extends BasePage {
+
+ applications = {
+ github: GithubPage
+ };
+
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor (page) {
+ super(page);
+ this.modal = page.getByTestId('add-app-connection-dialog');
+ this.searchInput = page.getByTestId('search-for-app-text-field');
+ this.appListItem = page.getByTestId('app-list-item');
+ this.appLoader = page.getByTestId('search-for-app-loader');
+ }
+
+ /**
+ * @param string link
+ */
+ async selectLink (link) {
+ if (this.applications[link] === undefined) {
+ throw {
+ message: `Unknown link "${link}" passed to ApplicationsModal.selectLink`
+ }
+ }
+ await this.searchInput.fill(link);
+ await this.appListItem.first().click();
+ return new this.applications[link](this.page);
+ }
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/applications-page.js b/packages/e2e-tests/fixtures/applications-page.js
index f7b6abc4..a84958e6 100644
--- a/packages/e2e-tests/fixtures/applications-page.js
+++ b/packages/e2e-tests/fixtures/applications-page.js
@@ -1,4 +1,5 @@
const path = require('node:path');
+const { ApplicationsModal } = require('./applications-modal');
const { AuthenticatedPage } = require('./authenticated-page');
export class ApplicationsPage extends AuthenticatedPage {
@@ -13,4 +14,9 @@ export class ApplicationsPage extends AuthenticatedPage {
this.drawerLink = this.page.getByTestId('apps-page-drawer-link');
this.addConnectionButton = this.page.getByTestId('add-connection-button');
}
+
+ async openAddConnectionModal () {
+ await this.addConnectionButton.click();
+ return new ApplicationsModal(this.page);
+ }
}
diff --git a/packages/e2e-tests/fixtures/apps/github/add-github-connection-modal.js b/packages/e2e-tests/fixtures/apps/github/add-github-connection-modal.js
new file mode 100644
index 00000000..f5cd6384
--- /dev/null
+++ b/packages/e2e-tests/fixtures/apps/github/add-github-connection-modal.js
@@ -0,0 +1,49 @@
+import { GithubPopup } from './github-popup';
+
+const { BasePage } = require('../../base-page');
+
+export class AddGithubConnectionModal extends BasePage {
+
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor (page) {
+ super(page);
+ this.modal = page.getByTestId('add-app-connection-dialog');
+ this.oauthRedirectInput = page.getByTestId('oAuthRedirectUrl-text');
+ this.clientIdInput = page.getByTestId('consumerKey-text');
+ this.clientIdSecretInput = page.getByTestId('consumerSecret-text');
+ this.submitButton = page.getByTestId('create-connection-button');
+ }
+
+ async visible () {
+ return await this.modal.isVisible();
+ }
+
+ async inputForm () {
+ await connectionModal.clientIdInput.fill(
+ process.env.GITHUB_CLIENT_ID
+ );
+ await connectionModal.clientIdSecretInput.fill(
+ process.env.GITHUB_CLIENT_SECRET
+ );
+ }
+
+ /**
+ * @returns {import('@playwright/test').Page}
+ */
+ async submit () {
+ const popupPromise = this.page.waitForEvent('popup');
+ await this.submitButton.click();
+ const popup = await popupPromise;
+ await popup.bringToFront();
+ return popup;
+ }
+
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ async handlePopup (page) {
+ return await GithubPopup.handle(page);
+ }
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/apps/github/github-page.js b/packages/e2e-tests/fixtures/apps/github/github-page.js
new file mode 100644
index 00000000..f1a05d5f
--- /dev/null
+++ b/packages/e2e-tests/fixtures/apps/github/github-page.js
@@ -0,0 +1,65 @@
+const { BasePage } = require('../../base-page');
+const { AddGithubConnectionModal } = require('./add-github-connection-modal');
+
+export class GithubPage extends BasePage {
+
+ constructor (page) {
+ super(page)
+ this.addConnectionButton = page.getByTestId('add-connection-button');
+ this.connectionsTab = page.getByTestId('connections-tab');
+ this.flowsTab = page.getByTestId('flows-tab');
+ this.connectionRows = page.getByTestId('connection-row');
+ this.flowRows = page.getByTestId('flow-row');
+ this.firstConnectionButton = page.getByTestId('connections-no-results');
+ this.firstFlowButton = page.getByTestId('flows-no-results');
+ this.addConnectionModal = new AddGithubConnectionModal(page);
+ }
+
+ async goto () {
+ await this.page.goto('/app/github/connections');
+ }
+
+ async openConnectionModal () {
+ await this.addConnectionButton.click();
+ await expect(this.addConnectionButton.modal).toBeVisible();
+ return this.addConnectionModal;
+ }
+
+ async flowsVisible () {
+ return this.page.url() === await this.flowsTab.getAttribute('href');
+ }
+
+ async connectionsVisible () {
+ return this.page.url() === await this.connectionsTab.getAttribute('href');
+ }
+
+ async hasFlows () {
+ if (!(await this.flowsVisible())) {
+ await this.flowsTab.click();
+ await expect(this.flowsTab).toBeVisible();
+ }
+ return await this.flowRows.count() > 0
+ }
+
+ async hasConnections () {
+ if (!(await this.connectionsVisible())) {
+ await this.connectionsTab.click();
+ await expect(this.connectionsTab).toBeVisible();
+ }
+ return await this.connectionRows.count() > 0;
+ }
+}
+
+/**
+ *
+ * @param {import('@playwright/test').Page} page
+ */
+export async function initGithubConnection (page) {
+ // assumes already logged in
+ const githubPage = new GithubPage(page);
+ await githubPage.goto();
+ const modal = await githubPage.openConnectionModal();
+ await modal.inputForm();
+ const popup = await modal.submit();
+ await modal.handlePopup(popup);
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/apps/github/github-popup.js b/packages/e2e-tests/fixtures/apps/github/github-popup.js
new file mode 100644
index 00000000..0bdb8579
--- /dev/null
+++ b/packages/e2e-tests/fixtures/apps/github/github-popup.js
@@ -0,0 +1,92 @@
+const { BasePage } = require('../../base-page');
+
+export class GithubPopup extends BasePage {
+
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ static async handle (page) {
+ const popup = new GithubPopup(page);
+ return await popup.handleAuthFlow();
+ }
+
+ getPathname () {
+ const url = this.page.url()
+ try {
+ return new URL(url).pathname;
+ } catch (e) {
+ return new URL(`https://github.com/${url}`).pathname;
+ }
+ }
+
+ async handleAuthFlow () {
+ if (this.getPathname() === '/login') {
+ await this.handleLogin();
+ }
+ if (this.page.isClosed()) { return; }
+ if (this.getPathname() === '/login/oauth/authorize') {
+ await this.handleAuthorize();
+ }
+ }
+
+ async handleLogin () {
+ const loginInput = this.page.getByLabel('Username or email address');
+ loginInput.click();
+ await loginInput.fill(process.env.GITHUB_USERNAME);
+ const passwordInput = this.page.getByLabel('Password');
+ passwordInput.click()
+ await passwordInput.fill(process.env.GITHUB_PASSWORD);
+ await this.page.getByRole('button', { name: 'Sign in' }).click();
+ // await this.page.waitForTimeout(2000);
+ if (this.page.isClosed()) {
+ return
+ }
+ // await this.page.waitForLoadState('networkidle', 30000);
+ this.page.waitForEvent('load');
+ if (this.page.isClosed()) {
+ return
+ }
+ await this.page.waitForURL(function (url) {
+ const u = new URL(url);
+ return (
+ u.pathname === '/login/oauth/authorize'
+ ) && u.searchParams.get('client_id');
+ });
+ }
+
+ async handleAuthorize () {
+ if (this.page.isClosed()) { return }
+ const authorizeButton = this.page.getByRole(
+ 'button',
+ { name: 'Authorize' }
+ );
+ await this.page.waitForEvent('load');
+ await authorizeButton.click();
+ await this.page.waitForURL(function (url) {
+ const u = new URL(url);
+ return (
+ u.pathname === '/login/oauth/authorize'
+ ) && (
+ u.searchParams.get('client_id') === null
+ );
+ })
+ const passwordInput = this.page.getByLabel('Password');
+ if (await passwordInput.isVisible()) {
+ await passwordInput.fill(process.env.GITHUB_PASSWORD);
+ const submitButton = this.page
+ .getByRole('button')
+ .filter({ hasText: /confirm|submit|enter|go|sign in/gmi });
+ if (await submitButton.isVisible()) {
+ submitButton.waitFor();
+ await expect(submitButton).toBeEnabled();
+ await submitButton.click();
+ } else {
+ throw {
+ page: this.page,
+ error: 'Could not find submit button for confirming user account'
+ };
+ }
+ }
+ await this.page.waitForEvent('close')
+ }
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/tests/app-integrations/github.spec.js b/packages/e2e-tests/tests/app-integrations/github.spec.js
new file mode 100644
index 00000000..8627f633
--- /dev/null
+++ b/packages/e2e-tests/tests/app-integrations/github.spec.js
@@ -0,0 +1,63 @@
+const { test, expect } = require('../../fixtures');
+
+test('Github OAuth integration', async ({ page, applicationsPage }) => {
+ const githubConnectionPage = await test.step(
+ 'Navigate to github connections modal',
+ async () => {
+ await applicationsPage.drawerLink.click();
+ if (page.url() !== '/apps') {
+ await page.waitForURL('/apps');
+ }
+ const connectionModal = await applicationsPage.openAddConnectionModal();
+ expect(connectionModal.modal).toBeVisible();
+ return await connectionModal.selectLink('github');
+ }
+ );
+
+ const connectionModal = await test.step(
+ 'Ensure the github connection modal is visible',
+ async () => {
+ const connectionModal = githubConnectionPage.addConnectionModal;
+ expect(connectionModal.modal).toBeVisible();
+ return connectionModal;
+ }
+ );
+
+ const githubPopup = await test.step(
+ 'Input data into the add connection form and submit',
+ async () => {
+ await connectionModal.clientIdInput.fill(process.env.GITHUB_CLIENT_ID);
+ await connectionModal.clientIdSecretInput.fill(
+ process.env.GITHUB_CLIENT_SECRET
+ );
+ return await connectionModal.submit();
+ }
+ );
+
+ await test.step('Ensure github popup is not a 404', async () => {
+ // expect(githubPopup).toBeVisible();
+ const title = await githubPopup.title();
+ expect(title).not.toMatch(/^Page not found/);
+ });
+
+ /* Skip these in CI
+ await test.step(
+ 'Handle github popup authentication flow',
+ async () => {
+ await connectionModal.handlePopup(githubPopup);
+ }
+ );
+
+ await test.step(
+ 'Ensure the new connection is added to the connections list',
+ async () => {
+ await page.locator('body').click({ position: { x: 0, y: 0 } });
+ // TODO
+ }
+ );
+ */
+});
+
+test.afterAll(async () => {
+ // TODO - Remove connections from github connections page
+});
\ No newline at end of file
diff --git a/packages/web/src/components/AppConnections/index.tsx b/packages/web/src/components/AppConnections/index.tsx
index 35dea8bc..46c99afd 100644
--- a/packages/web/src/components/AppConnections/index.tsx
+++ b/packages/web/src/components/AppConnections/index.tsx
@@ -29,6 +29,7 @@ export default function AppConnections(
);
}
diff --git a/packages/web/src/components/AppFlows/index.tsx b/packages/web/src/components/AppFlows/index.tsx
index 6e7e5ee0..4e40c5fd 100644
--- a/packages/web/src/components/AppFlows/index.tsx
+++ b/packages/web/src/components/AppFlows/index.tsx
@@ -45,6 +45,7 @@ export default function AppFlows(props: AppFlowsProps): React.ReactElement {
);
}
diff --git a/packages/web/src/components/InputCreator/index.tsx b/packages/web/src/components/InputCreator/index.tsx
index 0d701db4..52638c2d 100644
--- a/packages/web/src/components/InputCreator/index.tsx
+++ b/packages/web/src/components/InputCreator/index.tsx
@@ -190,6 +190,7 @@ export default function InputCreator(
onChange={onChange}
onBlur={onBlur}
name={computedName}
+ data-test={`${computedName}-text`}
label={label}
fullWidth
helperText={description}
diff --git a/packages/web/src/pages/Application/index.tsx b/packages/web/src/pages/Application/index.tsx
index f76a3dab..8db364c6 100644
--- a/packages/web/src/pages/Application/index.tsx
+++ b/packages/web/src/pages/Application/index.tsx
@@ -175,6 +175,7 @@ export default function Application(): React.ReactElement | null {
value={URLS.APP_CONNECTIONS_PATTERN}
disabled={!app.supportsConnections}
component={Link}
+ data-test="connections-tab"
/>