Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions .github/workflows/pr-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ jobs:
SKIP_INSTALLATION: true
steps:
- uses: actions/checkout@v4
with:
with:
path: podman-desktop-sandbox-ext

# Install nodejs
Expand Down Expand Up @@ -195,7 +195,7 @@ jobs:
- name: Execute yarn in Sandbox extension
working-directory: ./podman-desktop-sandbox-ext
run: yarn install

- name: Build Sandbox extension from container file
working-directory: ./podman-desktop-sandbox-ext
run: |
Expand All @@ -211,7 +211,7 @@ jobs:
working-directory: ./podman-desktop-sandbox-ext
run: |
rm -rf tests/playwright/output/sandbox-tests-pd/plugins/extension/node_modules/electron/dist/resources

- name: Run E2E tests
working-directory: ./podman-desktop-sandbox-ext
env:
Expand All @@ -222,4 +222,4 @@ jobs:
if: always()
with:
name: e2e-tests
path: ./**/tests/**/output/
path: ./**/tests/**/output/
190 changes: 190 additions & 0 deletions tests/src/developer-sandbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,17 @@ import {
ExtensionCardPage,
RunnerOptions,
test,
ResourceConnectionCardPage,
startChromium,
findPageWithTitleInBrowser,
ConfirmInputValue,
KubeContextPage,
performBrowserLogin,
} from '@podman-desktop/tests-playwright';
import { DeveloperSandboxPage } from './model/pages/developer-sandbox-page';
import { CreateResourcePage } from './model/pages/create-resource-page';
import type { Browser, BrowserContext, Page } from '@playwright/test';
import path, { join } from 'node:path';

let extensionInstalled = false;
let extensionCard: ExtensionCardPage;
Expand All @@ -37,6 +46,12 @@ const activeExtensionStatus = 'ACTIVE';
const disabledExtensionStatus = 'DISABLED';
const activeConnectionStatus = 'RUNNING';
const skipInstallation = process.env.SKIP_INSTALLATION === 'true';
let browserOutputPath: string;
let loginCommand = '';
const resourceCardLabel = 'redhat.sandbox';
const resourceName = 'Developer Sandbox';
const contextName = 'dev-sandbox-context-3';
const chromePort = '9222';

test.use({
runnerOptions: new RunnerOptions({ customFolder: 'sandbox-tests-pd', autoUpdate: false, autoCheckUpdates: false }),
Expand All @@ -45,6 +60,8 @@ test.beforeAll(async ({ runner, page, welcomePage }) => {
runner.setVideoAndTraceName('sandbox-e2e');
await welcomePage.handleWelcomePage(true);
extensionCard = new ExtensionCardPage(page, extensionLabelName, extensionLabel);
browserOutputPath = test.info().project.outputDir;
console.log(`Saving browser test artifacts to: '${browserOutputPath}'`);
});

test.afterAll(async ({ runner }) => {
Expand Down Expand Up @@ -140,6 +157,168 @@ test.describe.serial('Red Hat Developer Sandbox extension verification', () => {
});
});

test.describe.serial('Developer Sandbox cluster verification', async () => {
test.describe.serial('Fetch login command via browser', async () => {
let chromiumPage: Page | undefined;
let browser: Browser | undefined;
let context: BrowserContext | undefined;

test.afterAll(async () => {
if (browser) {
console.log('Stopping tracing and closing browser...');
await context?.tracing.stop({
path: join(path.join(browserOutputPath), 'traces', 'browser-sandbox-trace.zip'),
});
if (chromiumPage) {
await chromiumPage.close();
}
await browser.close();
}
});

test('Open Developer Sandbox page in browser', async ({ navigationBar, page }) => {
test.setTimeout(120_000);
//get sandbox url
const settingsBar = await navigationBar.openSettings();
await settingsBar.resourcesTab.click();
const resourcesPage = new ResourcesPage(page);
playExpect(await resourcesPage.resourceCardIsVisible(resourceCardLabel)).toBeTruthy();
const createNewSandboxButton = page.getByRole('button', { name: `Create new ${resourceName}` }); //goToCreateNewResourcePage only takes 1 argument
await createNewSandboxButton.click();
const createResourcePage = new CreateResourcePage(page);
await createResourcePage.logIntoSandboxButton.click();
const websiteDialog = page.getByRole('dialog', { name: 'Open External Website' });
await playExpect(websiteDialog).toBeVisible();
const sandboxUrl = await websiteDialog.getByLabel('Dialog Details').textContent();
const cancelDialogButton = websiteDialog.getByRole('button', { name: 'Cancel' });
await cancelDialogButton.click();

//open the website
if (sandboxUrl) {
browser = await startChromium(chromePort, path.join(browserOutputPath));
context = await browser.newContext();
await context.tracing.start({ screenshots: true, snapshots: true, sources: true });
const newPage = await context.newPage();
await newPage.goto(sandboxUrl);
await newPage.waitForURL(/developers.redhat.com/);
chromiumPage = newPage;
if (browser) {
await findPageWithTitleInBrowser(browser, 'Developer Sandbox | Red Hat Developer');
}
console.log(`Found page with title: ${await chromiumPage?.title()}`);
} else {
throw new Error('Did not find Developer Sandbox page');
}
});
test('Log into Red Hat Sandbox', async () => {
//go to login page
playExpect(chromiumPage).toBeDefined();
if (!chromiumPage) {
throw new Error('Chromium browser page was not initialized');
}
await chromiumPage.bringToFront();
console.log(`Switched to Chrome tab with title: ${await chromiumPage.title()}`);
const startSandboxButton = chromiumPage.getByRole('button', { name: 'Start your sandbox for free' });
await playExpect(startSandboxButton).toBeVisible();
await startSandboxButton.click();

//log in, same tab
const usernameAction: ConfirmInputValue = {
inputLocator: chromiumPage.getByRole('textbox', { name: 'username' }),
inputValue: process.env.DVLPR_USERNAME ?? 'unknown',
confirmLocator: chromiumPage.getByRole('button', { name: 'Next' }),
};
const passwordAction: ConfirmInputValue = {
inputLocator: chromiumPage.getByRole('textbox', { name: 'password' }),
inputValue: process.env.DVLPR_PASSWORD ?? 'unknown',
confirmLocator: chromiumPage.getByRole('button', { name: 'Log in' }),
};
const usernameBox = chromiumPage.getByRole('textbox', { name: 'Red Hat login' });
await playExpect(usernameBox).toBeVisible({ timeout: 5_000 });
await usernameBox.focus();

//after login redirect twice to sandbox.redhat.com, same tab
await performBrowserLogin(chromiumPage, /Log In/, usernameAction, passwordAction, async chromiumPage => {
playExpect(chromiumPage).toBeDefined();
if (!chromiumPage) {
throw new Error('Chromium browser page was not initialized');
}
playExpect(await chromiumPage.title()).toBe('Developer Sandbox | Developer Sandbox');
await chromiumPage.screenshot({
path: join(path.join(browserOutputPath), 'screenshots', 'after_login_in_browser.png'),
type: 'png',
fullPage: true,
});
});
});
test('Fetch the login command', async () => {
//open "try it" openshift
playExpect(chromiumPage).toBeDefined();
if (!chromiumPage) {
throw new Error('Chromium browser page was not initialized');
}
await chromiumPage.bringToFront();
const openshiftBoxLabel = chromiumPage.getByAltText('Openshift', { exact: true });
await playExpect(openshiftBoxLabel).toBeVisible();
const openshiftBox = openshiftBoxLabel.locator('..').locator('..').locator('..');
const tryItButton = openshiftBox.getByRole('button', { name: 'Try it' });
await playExpect(tryItButton).toBeVisible();
await tryItButton.click();

//new tab, log in through the Openshift auth page (sometimes might need reload)
await loginThroughOpenshiftServicePage(browser!, chromiumPage);

//same tab, get login command from the Console Openshift page
const userDropdownMenuButton = chromiumPage.getByRole('button', { name: 'User menu' });
await playExpect(userDropdownMenuButton).toBeVisible({ timeout: 50_000 });
await userDropdownMenuButton.click();
const copyLoginCommandButton = chromiumPage.getByText('Copy login command');
await playExpect(copyLoginCommandButton).toBeVisible();
await copyLoginCommandButton.click();

//new tab, find command (sandbox login might need reload)
await loginThroughOpenshiftServicePage(browser!, chromiumPage);

const displayTokenButton = chromiumPage.getByRole('button', { name: 'Display Token' });
await playExpect(displayTokenButton).toBeVisible();
await displayTokenButton.click();
const commandElement = chromiumPage.getByText('oc login').locator('..');
await playExpect(commandElement).toBeVisible();
loginCommand = await commandElement.innerText();
});
});

test('Create Sandbox cluster', async ({ page }) => {
await page.bringToFront();
const createResourcePage = new CreateResourcePage(page);
await createResourcePage.createResource(loginCommand, contextName);
});

test('Verify Sandbox cluster and context', async ({ page, navigationBar }) => {
const sandboxClusterCard = new ResourceConnectionCardPage(page, resourceCardLabel, contextName);
playExpect(await sandboxClusterCard.doesResourceElementExist()).toBeTruthy();
await playExpect(sandboxClusterCard.resourceElementConnectionStatus).toHaveText('RUNNING');

const settingsBar = await navigationBar.openSettings();
await settingsBar.kubernetesTab.click();
const kubeContextPage = new KubeContextPage(page);
playExpect(await kubeContextPage.pageIsEmpty()).not.toBeTruthy();
playExpect(await kubeContextPage.isContextReachable(contextName)).toBeTruthy();
playExpect(await kubeContextPage.isContextDefault(contextName)).not.toBeTruthy();
});

test('Delete remote cluster context', async ({ page, navigationBar }) => {
const kubeContextPage = new KubeContextPage(page);
await kubeContextPage.deleteContext(contextName);
playExpect(await kubeContextPage.pageIsEmpty()).toBeTruthy();

const settingsBar = await navigationBar.openSettings();
await settingsBar.resourcesTab.click();
const sandboxClusterCard = new ResourceConnectionCardPage(page, resourceCardLabel, contextName);
playExpect(await sandboxClusterCard.doesResourceElementExist()).not.toBeTruthy();
});
});

test('Extension can be removed', async ({ navigationBar }) => {
await removeExtension(navigationBar);
});
Expand Down Expand Up @@ -184,3 +363,14 @@ async function checkSandboxInDashboard(navigationBar: NavigationBar, isInstalled
await playExpect(sandboxProviderCard).toBeHidden();
}
}

async function loginThroughOpenshiftServicePage(browser: Browser, chromiumPage: Page) {
let loginSandboxPage = await findPageWithTitleInBrowser(browser!, 'Login - Red Hat OpenShift Service on AWS');
if (!loginSandboxPage) {
throw new Error('Sandbox service login browser page was not initialized');
}
await loginSandboxPage.bringToFront();
const loginWithSandboxButton = chromiumPage.getByRole('button', { name: 'Log in with DevSandbox' });
await playExpect(loginWithSandboxButton).toBeVisible();
await loginWithSandboxButton.click();
}
68 changes: 68 additions & 0 deletions tests/src/model/pages/create-resource-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed 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 CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import type { Page, Locator } from '@playwright/test';
import { BasePage, ResourcesPage, expect as playExpect } from '@podman-desktop/tests-playwright';

export class CreateResourcePage extends BasePage {
readonly heading: Locator;
readonly content: Locator;
readonly logIntoSandboxButton: Locator;
readonly contextName: Locator;
readonly setAsCurrentContext: Locator;
readonly loginCommand: Locator;
readonly closeButton: Locator;
readonly createButton: Locator;

constructor(page: Page) {
super(page);
this.heading = this.page.getByRole('heading', { name: 'Create Developer Sandbox' });
this.content = this.page.getByRole('region', { name: 'Tab Content' });
this.logIntoSandboxButton = this.page.getByRole('button', { name: 'Log into Developer Sandbox' });
this.contextName = this.page.getByRole('textbox', { name: 'Context name' });
this.setAsCurrentContext = this.page.getByRole('checkbox', { name: 'Set as current context' });
this.loginCommand = this.page.getByRole('textbox', { name: 'Login command from Developer Console' });
this.closeButton = this.page.getByRole('button', { name: 'Close page' });
this.createButton = this.page.getByRole('button', { name: 'Create' });
}

async createResource(
loginCommandValue: string,
contextNameValue?: string,
setAsCurrentContextValue = false,
): Promise<ResourcesPage> {
await this.loginCommand.fill(loginCommandValue);

if (contextNameValue) {
await this.contextName.fill(contextNameValue);
}

if (setAsCurrentContextValue !== (await this.setAsCurrentContext.isChecked())) {
await this.setAsCurrentContext.locator('..').click();
playExpect(await this.setAsCurrentContext.isChecked()).toBe(setAsCurrentContextValue);
}

const successMessage = this.page.getByText('Successful operation');
const goToResourcesButton = this.page.getByRole('button', { name: 'Go back to resources' });
await playExpect(successMessage).toBeVisible();
await playExpect(goToResourcesButton).toBeVisible();

await goToResourcesButton.click();
return new ResourcesPage(this.page);
}
}
30 changes: 15 additions & 15 deletions tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"compilerOptions": {
"strictNullChecks": true,
"lib": [ "ES2017", "webworker" ],
"module": "esnext",
"target": "esnext",
"sourceMap": true,
"rootDir": "src",
"outDir": "dist",
"skipLibCheck": true,
"types": [ "node" ],
"allowSyntheticDefaultImports": true,
"moduleResolution": "Node",
"esModuleInterop": true
}
}
"compilerOptions": {
"strictNullChecks": true,
"lib": ["ES2017", "webworker"],
"module": "esnext",
"target": "esnext",
"sourceMap": true,
"rootDir": "src",
"outDir": "dist",
"skipLibCheck": true,
"types": ["node"],
"allowSyntheticDefaultImports": true,
"moduleResolution": "Node",
"esModuleInterop": true
}
}
Loading