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

Sandbox-Playwright page object model #19

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
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
90 changes: 90 additions & 0 deletions e2e/helm.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import path from 'path';
import {
ElectronApplication, BrowserContext, _electron, Page, Locator
} from 'playwright';
import { test, expect } from '@playwright/test';
import {
createDefaultSettings, kubectl, helm, tearDownHelm, playwrightReportAssets
} from './utils/TestUtils';
import { NavPage } from './pages/nav-page';

let page: Page;

test.describe.serial('Helm Deployment Test', () => {
let electronApp: ElectronApplication;
let context: BrowserContext;

test.beforeAll(async() => {
createDefaultSettings();

electronApp = await _electron.launch({
args: [
path.join(__dirname, '../'),
'--disable-gpu',
'--whitelisted-ips=',
'--disable-dev-shm-usage',
]
});
context = electronApp.context();

await context.tracing.start({ screenshots: true, snapshots: true });
page = await electronApp.firstWindow();
});

/**
* helm teardown
* It should run outside of the electronApp.close(), just to make sure the teardown won't
* affect the shutdown process in case of exceptions/errors.
*/
test.afterAll(tearDownHelm);

test.afterAll(async() => {
await context.tracing.stop({ path: playwrightReportAssets(path.basename(__filename)) });
await electronApp.close();
});

test('should start loading the background services', async() => {
const navPage = new NavPage(page);

await navPage.getProgressBar();
});

test('should check kubernetes API is ready', async() => {
const output = await kubectl('cluster-info');

expect(output).toMatch(/is running at ./);
});

test('should add helm sample repository', async() => {
const helmAddRepoOutput = await helm('repo', 'add', 'bitnami', 'https://charts.bitnami.com/bitnami');

// Sanity check for local test execution
// if the helm repository already exist locally
if (helmAddRepoOutput.includes('already exists')) {
expect(helmAddRepoOutput).toContain('"bitnami" already exists with the same configuration, skipping');
} else {
expect(helmAddRepoOutput).toContain('"bitnami" has been added to your repositories');
}
});
test('should install helm sample application and check if it was deployed', async() => {
const helmInstall = await helm('upgrade', '--install', '--wait', '--timeout=20m', 'nginx-sample',
'bitnami/nginx', '--set=service.type=NodePort', '--set=volumePermissions.enabled=true');

expect(helmInstall).toContain('STATUS: deployed');
});
test('should verify if the application was properly deployed/installed', async() => {
// Get Node IP address.
const nodeIpAddress = (await kubectl('get', 'nodes', '--output=jsonpath={.items[0].status.addresses[0].address}')).trim();

// Get Node Port number.
const nodePortNumber = (await kubectl('get', '--namespace', 'default', '--output=jsonpath={.spec.ports[0].nodePort}', 'services', 'nginx-sample')).trim();

const podName = (await kubectl('get', 'pods', '--output=name', '--namespace', 'default')).trim();

// Check is the app is running
const checkAppStatus = await kubectl('exec', '--namespace', 'default', '--stdin', '--tty',
podName, '--', 'curl', '--fail', `${ nodeIpAddress }:${ nodePortNumber }`);

expect(checkAppStatus).toContain('Welcome to nginx!');
});
});
30 changes: 10 additions & 20 deletions e2e/kubectl.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@ import {
ElectronApplication, BrowserContext, _electron, Page, Locator
} from 'playwright';
import { test, expect } from '@playwright/test';
import { createDefaultSettings, kubectl } from './utils/TestUtils';
import { createDefaultSettings, kubectl, playwrightReportAssets } from './utils/TestUtils';
import { NavPage } from './pages/nav-page';

let page: Page;
const defaultReportFolder = path.join(__dirname, 'reports/');

test.describe.serial('K8s Deployment Test', () => {
let mainTitle: Locator;
let electronApp: ElectronApplication;
let context: BrowserContext;

const mainTitleSelector = '[data-test="mainTitle"]';

test.beforeAll(async() => {
createDefaultSettings();

Expand All @@ -33,27 +30,20 @@ test.describe.serial('K8s Deployment Test', () => {
});

test.afterAll(async() => {
await context.tracing.stop({ path: path.join(defaultReportFolder, 'pw-trace.zip') });
await context.tracing.stop({ path: playwrightReportAssets(path.basename(__filename)) });
await electronApp.close();
});

test('should load Rancher Desktop App', async() => {
mainTitle = page.locator(mainTitleSelector);

await expect(mainTitle).toHaveText('Welcome to Rancher Desktop');
});

test('should start loading the background services', async() => {
const progressBarSelector = page.locator('.progress');
const navPage = new NavPage(page);

await progressBarSelector.waitFor({ state: 'detached', timeout: 300_000 });
await expect(progressBarSelector).toBeHidden();
await navPage.getProgressBar();
});

test('should run Kubernetes on Rancher Desktop (kubectl)', async() => {
const output = await kubectl('cluster-info');

await expect(output).toMatch(/is running at ./);
expect(output).toMatch(/is running at ./);
});

test('should create a sample namespace', async() => {
Expand All @@ -64,7 +54,7 @@ test.describe.serial('K8s Deployment Test', () => {
const namespaces = (await kubectl('get', 'namespace', '--output=name')).trim();
const testNamespace = namespaces.split('\n');

await expect(testNamespace).toContain('namespace/rd-nginx-demo');
expect(testNamespace).toContain('namespace/rd-nginx-demo');
});
test('should deploy sample nginx server', async() => {
try {
Expand All @@ -76,8 +66,8 @@ test.describe.serial('K8s Deployment Test', () => {
const podName = (await kubectl('get', 'pods', '--output=name', '--namespace', 'rd-nginx-demo')).trim();
const checkAppStatus = await kubectl('exec', '--namespace', 'rd-nginx-demo', '-it', podName, '--', 'curl', '--fail', 'localhost');

await expect(await kubectl('get', 'pods', '--output=name', '--namespace', 'rd-nginx-demo')).toBeTruthy();
await expect(checkAppStatus).toContain('Welcome to nginx!');
expect(await kubectl('get', 'pods', '--output=name', '--namespace', 'rd-nginx-demo')).toBeTruthy();
expect(checkAppStatus).toContain('Welcome to nginx!');
} catch (err:any) {
console.error('Error: ');
console.error(`stdout: ${ err.stdout }`);
Expand All @@ -91,6 +81,6 @@ test.describe.serial('K8s Deployment Test', () => {
const namespaces = (await kubectl('get', 'namespace', '--output=name')).trim();
const nginxSampleNamespace = namespaces.split('\n');

await expect(nginxSampleNamespace).not.toContain('namespace/rd-nginx-demo');
expect(nginxSampleNamespace).not.toContain('namespace/rd-nginx-demo');
});
});
105 changes: 39 additions & 66 deletions e2e/main.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import path from 'path';
import {
ElectronApplication, BrowserContext, _electron, Page, Locator
} from 'playwright';
import { test, expect } from '@playwright/test';
import { createDefaultSettings } from './utils/TestUtils';
import { test } from '@playwright/test';
import { createDefaultSettings, playwrightReportAssets } from './utils/TestUtils';
import { NavPage } from './pages/nav-page';
import { K8sPage } from './pages/k8s-page';
import { WslPage } from './pages/wsl-page';
import { PortForwardPage } from './pages/portforward-page';

let page: Page;
const defaultReportFolder = path.join(__dirname, 'reports/');

/**
* Using test.describe.serial make the test execute step by step, as described on each `test()` order
* Playwright executes test in parallel by default and it will not work for our app backend loading process.
* */
test.describe.serial('Main App Test', () => {
let mainTitle: Locator;
let electronApp: ElectronApplication;
let context: BrowserContext;
const mainTitleSelector = '[data-test="mainTitle"]';

test.beforeAll(async() => {
createDefaultSettings();
Expand All @@ -37,71 +38,59 @@ test.describe.serial('Main App Test', () => {
});

test.afterAll(async() => {
await context.tracing.stop({ path: path.join(defaultReportFolder, 'pw-trace.zip') });
await context.tracing.stop({ path: playwrightReportAssets(path.basename(__filename)) });
await electronApp.close();
});

test('should land on General page', async() => {
mainTitle = page.locator(mainTitleSelector);
const navPage = new NavPage(page);

await expect(mainTitle).toHaveText('Welcome to Rancher Desktop');
await navPage.getGeneralPageTile('Welcome to Rancher Desktop');
});

test('should start loading the background services and hide progress bar', async() => {
const progressBarSelector = page.locator('.progress');
const navPage = new NavPage(page);

await progressBarSelector.waitFor({ state: 'detached', timeout: 120_000 });
await expect(progressBarSelector).toBeHidden();
await navPage.getProgressBar();
});

test('should navigate to Kubernetes Settings and check elements', async() => {
const k8sMemorySliderSelector = '[id="memoryInGBWrapper"]';
const k8sCpuSliderSelector = '[id="numCPUWrapper"]';
const k8sPortSelector = '[data-test="portConfig"]';
const k8sResetBtn = '[data-test="k8sResetBtn"]';

await navigateTo('K8s');
// Collecting data from selectors
const k8sSettingsTitle = page.locator(mainTitleSelector);
const k8sMemorySlider = page.locator(k8sMemorySliderSelector);
const k8sCpuSlider = page.locator(k8sCpuSliderSelector);
const k8sPort = page.locator(k8sPortSelector);
const k8sResetButton = page.locator(k8sResetBtn);
const navPage = new NavPage(page);
const k8sPage = new K8sPage(page);

await navPage.navigateTo('K8s');

if (!os.platform().startsWith('win')) {
await expect(k8sMemorySlider).toBeVisible();
await expect(k8sCpuSlider).toBeVisible();
await k8sPage.getK8sMemorySlider();
await k8sPage.getK8sCpuSlider();
}

await expect(k8sSettingsTitle).toHaveText('Kubernetes Settings');
await expect(k8sPort).toBeVisible();
await expect(k8sResetButton).toBeVisible();
await navPage.getGeneralPageTile('Kubernetes Settings');
await k8sPage.getK8sPort();
await k8sPage.getK8sResetButton();
});

/**
* Checking WSL and Port Forwarding - Windows Only
*/
if (os.platform().startsWith('win')) {
test('should navigate to WSL Integration and check elements', async() => {
const wslDescriptionSelector = '.description';
const navPage = new NavPage(page);
const wslPage = new WslPage(page);

await navigateTo('Integrations');
const getWslIntegrationTitle = page.locator(mainTitleSelector);
const getWslDescriptionText = page.locator(wslDescriptionSelector);
await navPage.navigateTo('Integrations');

await expect(getWslIntegrationTitle).toHaveText('WSL Integration');
await expect(getWslDescriptionText).toBeVisible();
await navPage.getGeneralPageTile('WSL Integration');
await wslPage.getWslDescription();
});

test('should navigate to Port Forwarding and check elements', async() => {
const portForwardingContentSelector = '.content';

await navigateTo('PortForwarding');
const getPortForwardingTitle = page.locator(mainTitleSelector);
const getPortForwardingContent = page.locator(portForwardingContentSelector);
const navPage = new NavPage(page);
const portForwardPage = new PortForwardPage(page);

await expect(getPortForwardingTitle).toHaveText('Port Forwarding');
await expect(getPortForwardingContent).toBeVisible();
await navPage.navigateTo('PortForwarding');
await navPage.getGeneralPageTile('Port Forwarding');
await portForwardPage.getPortForwardDescription();
});
}

Expand All @@ -110,40 +99,24 @@ test.describe.serial('Main App Test', () => {
*/
if (!os.platform().startsWith('win')) {
test('should navigate to Supporting Utilities and check elements', async() => {
await navigateTo('Integrations');
const getSupportTitle = page.locator(mainTitleSelector);
const navPage = new NavPage(page);

await expect(getSupportTitle).toHaveText('Supporting Utilities');
await navPage.navigateTo('Integrations');
await navPage.getGeneralPageTile('Supporting Utilities');
});
}

test('should navigate to Images page', async() => {
const getSupportTitle = page.locator(mainTitleSelector);
const navPage = new NavPage(page);

await navigateTo('Images');
await expect(getSupportTitle).toHaveText('Images');
await navPage.navigateTo('Images');
await navPage.getGeneralPageTile('Images');
});

test('should navigate to Troubleshooting and check elements', async() => {
const getSupportTitle = page.locator(mainTitleSelector);
const navPage = new NavPage(page);

await navigateTo('Troubleshooting');
await expect(getSupportTitle).toHaveText('Troubleshooting');
await navPage.navigateTo('Troubleshooting');
await navPage.getGeneralPageTile('Troubleshooting');
});
});

/**
* Navigate to a specific tab
* @param path
*/
async function navigateTo(path: string) {
try {
return await Promise.all([
page.click(`.nav li[item="/${ path }"] a`),
page.waitForNavigation({ url: `**/${ path }`, timeout: 60_000 }),
page.screenshot({ path: `${ defaultReportFolder }${ path }-screenshot.png` })
]);
} catch (err) {
console.log(`Cannot navigate to ${ path }. Error ---> `, err);
}
}
34 changes: 34 additions & 0 deletions e2e/pages/k8s-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Page, Locator } from 'playwright';
import { expect } from '@playwright/test';

export class K8sPage {
readonly page: Page;
readonly k8sMemorySliderSelector: Locator;
readonly k8sCpuSliderSelector: Locator;
readonly k8sPortSelector: Locator;
readonly k8sResetBtn: Locator;

constructor(page: Page) {
this.page = page;
this.k8sMemorySliderSelector = page.locator('[id="memoryInGBWrapper"]');
this.k8sCpuSliderSelector = page.locator('[id="numCPUWrapper"]');
this.k8sPortSelector = page.locator('[data-test="portConfig"]');
this.k8sResetBtn = page.locator('[data-test="k8sResetBtn"]');
}

async getK8sMemorySlider() {
await expect(this.k8sMemorySliderSelector).toBeVisible();
}

async getK8sCpuSlider() {
await expect(this.k8sCpuSliderSelector).toBeVisible();
}

async getK8sPort() {
await expect(this.k8sPortSelector).toBeVisible();
}

async getK8sResetButton() {
await expect(this.k8sResetBtn).toBeVisible();
}
}
Loading