Vagrantfile
*.xls
+node_modules
+node
--- /dev/null
+normal['version']="1.16.0"
--- /dev/null
+# SDC Playwright E2E Tests
+
+Playwright-based end-to-end tests for the SDC frontend. These tests run against the same
+Docker stack used by the existing Selenium/TestNG integration tests (Cassandra, backend,
+frontend, webseal-simulator, etc.) but use [Playwright](https://playwright.dev/) instead
+of Selenium for browser automation.
+
+## Prerequisites
+
+- Node.js 18+ (Playwright ≥ 1.42 requires it)
+- Docker
+- Ports 8080, 8285, 8443, 9042, 9443 free on the host
+
+## Running locally
+
+The tests need the integration-test Docker stack (Cassandra, backend, frontend,
+simulator). If the stack is already running, skip straight to "Run the tests".
+
+### 1. Build Docker images (one-time)
+
+The Docker images (`onap/sdc-backend-all-plugins`, `onap/sdc-frontend`, etc.)
+must exist locally. Build them from the repository root with **both** the
+`all` and `docker` profiles (`-P all` keeps the default module list active
+when other profiles are specified):
+
+```bash
+mvn clean install -P all,docker -DskipTests
+```
+
+> **Note:** `clean` is required so that the `build-helper-maven-plugin`
+> `parse-version` goal (bound to `pre-clean`) runs and resolves
+> `${parsedVersion.*}` variables used in Docker image tags.
+
+### 2. Start the Docker stack
+
+```bash
+mvn pre-integration-test -P run-integration-tests-playwright \
+ -f integration-tests/pom.xml
+```
+
+`pre-integration-test` starts the containers (Cassandra, backend, frontend,
+simulator, etc.) but does **not** run the tests or tear them down, so the
+stack stays up for iterating locally.
+
+Once healthy, the webseal-simulator is reachable at `http://localhost:8285`.
+
+Stop the containers later with:
+
+```bash
+mvn docker:stop -f integration-tests/pom.xml
+```
+
+### 3. Run the tests
+
+```bash
+cd integration-tests/playwright-tests
+npm install
+npx playwright install chromium
+SDC_BASE_URL=http://localhost:8285 npx playwright test
+```
+
+### Headed mode (see the browser)
+
+```bash
+SDC_BASE_URL=http://localhost:8285 npm run test:headed
+```
+
+### View the HTML report
+
+```bash
+npm run test:report
+```
+
+## Running via Maven (full lifecycle)
+
+A single Maven command handles the entire lifecycle — spin up Docker, install
+Node/npm, install Playwright browsers, run the tests, tear down Docker:
+
+```bash
+mvn verify -P run-integration-tests-playwright \
+ -f integration-tests/pom.xml
+```
+
+> This assumes the Docker images already exist locally (see step 1 above).
+
+Reports are written to:
+
+| Artifact | Path |
+| -------------------- | --------------------------------------------------------- |
+| HTML report | `integration-tests/target/playwright-report/index.html` |
+| JUnit XML | `integration-tests/target/playwright-reports/results.xml` |
+| Screenshots & traces | `integration-tests/target/playwright-results/` |
+
+## CI (Jenkins)
+
+The JJB definition in `ci-management` registers the job
+`sdc-integration-tests-{stream}-playwright-verify-java`, which triggers on every
+Gerrit patch set and archives the report artifacts listed above.
+
+## Writing new tests
+
+Add `.spec.ts` files under `tests/`. The Playwright config (`playwright.config.ts`)
+sets `baseURL` from the `SDC_BASE_URL` environment variable (default
+`http://localhost:8285`), so you can use relative URLs in `page.goto()`.
+
+The webseal-simulator login flow is straightforward:
+
+```ts
+await page.goto("/login");
+await page.locator('input[name="userId"]').fill("<userId>");
+await page.locator('input[name="password"]').fill("123123a");
+await page.locator('input[value="Login"]').click();
+await page.waitForURL("**/sdc1**");
+```
+
+See `tests/sdc-sanity.spec.ts` for a working example.
--- /dev/null
+{
+ "name": "sdc-playwright-tests",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "sdc-playwright-tests",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@playwright/test": "^1.42.0"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.58.2",
+ "resolved": "https://artifactory.devops.telekom.de/artifactory/api/npm/registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
+ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@playwright/test/node_modules/playwright": {
+ "version": "1.58.2",
+ "resolved": "https://artifactory.devops.telekom.de/artifactory/api/npm/registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
+ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://artifactory.devops.telekom.de/artifactory/api/npm/registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.58.2",
+ "resolved": "https://artifactory.devops.telekom.de/artifactory/api/npm/registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
+ "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ }
+ }
+}
--- /dev/null
+{
+ "name": "sdc-playwright-tests",
+ "version": "1.0.0",
+ "description": "Playwright end-to-end tests for SDC",
+ "private": true,
+ "scripts": {
+ "test": "npx playwright test",
+ "test:headed": "npx playwright test --headed",
+ "test:report": "npx playwright show-report"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.42.0"
+ }
+}
--- /dev/null
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ testDir: './tests',
+ timeout: 60_000,
+ expect: {
+ timeout: 10_000,
+ },
+ fullyParallel: false,
+ retries: 1,
+ reporter: [
+ ['list'],
+ ['html', { outputFolder: '../target/playwright-report', open: 'never' }],
+ ['junit', { outputFile: '../target/playwright-reports/results.xml' }],
+ ],
+ use: {
+ baseURL: process.env.SDC_BASE_URL || 'http://localhost:8285',
+ trace: 'on',
+ screenshot: 'only-on-failure',
+ ignoreHTTPSErrors: true,
+ },
+ projects: [
+ {
+ name: 'chromium',
+ use: {
+ browserName: 'chromium',
+ viewport: { width: 1920, height: 1080 },
+ },
+ },
+ ],
+ outputDir: '../target/playwright-results',
+});
--- /dev/null
+import { test, expect } from '@playwright/test';
+
+/**
+ * SDC Sanity Test - Demonstrates Playwright e2e testing with the integration-test Docker stack.
+ *
+ * This test uses the webseal-simulator (sdc-sim) login page to authenticate,
+ * then verifies that the SDC home page loads successfully.
+ *
+ * When running locally: SDC_BASE_URL=http://localhost:8285 npx playwright test
+ * When running in CI: The Maven profile sets the URL automatically.
+ */
+
+const SIM_PASSWORD = '123123a';
+
+test.describe('SDC Sanity', () => {
+
+ test('should login via simulator and reach the SDC home page', async ({ page }) => {
+ // Navigate to the simulator login page
+ await page.goto('/login');
+
+ // Verify the login page rendered
+ await expect(page.locator('h1')).toContainText('Webseal simulator');
+
+ // Fill in credentials for the Designer role
+ await page.locator('input[name="userId"]').fill('cs0008');
+ await page.locator('input[name="password"]').fill(SIM_PASSWORD);
+
+ // Submit the login form
+ await page.locator('input[value="Login"]').click();
+
+ // After login the simulator redirects to /sdc1 which loads the SDC UI.
+ // Wait for the URL to contain /sdc1 (the redirect target).
+ await page.waitForURL('**/sdc1**', { timeout: 30_000 });
+
+ // The SDC UI should render – verify the page title or a known element.
+ // The SDC app sets the document title to "SDC" or similar.
+ await expect(page).toHaveTitle(/SDC|STARTER/i, { timeout: 30_000 });
+
+ // The HOME button in the main menu should be visible
+ await expect(page.locator('[data-tests-id="main-menu-button-home"]')).toBeVisible({ timeout: 30_000 });
+ });
+
+ test('should display user quick-links table on the login page', async ({ page }) => {
+ await page.goto('/login');
+
+ // The simulator renders a table of preconfigured users
+ const table = page.locator('table');
+ await expect(table).toBeVisible();
+
+ // At least one user row should be present
+ const rows = table.locator('tr');
+ await expect(rows).not.toHaveCount(0);
+ });
+});
</plugins>
</build>
</profile>
+ <profile>
+ <!-- Playwright-based UI integration tests.
+ Reuses the same Docker stack as the Selenium tests but runs
+ Playwright (Node.js) instead of TestNG + Selenium.
+
+ Usage:
+ mvn verify -P run-integration-tests-playwright -f integration-tests/pom.xml
+ -->
+ <id>run-integration-tests-playwright</id>
+ <properties>
+ <skipYamlJsonValidator>true</skipYamlJsonValidator>
+ <checkstyle.skip>true</checkstyle.skip>
+ <surefire.skip.tests>true</surefire.skip.tests>
+ <skipTest>false</skipTest>
+ <it.playwright.baseUrl>http://localhost:8285</it.playwright.baseUrl>
+ <it.playwright.nodeVersion>v18.20.8</it.playwright.nodeVersion>
+ <it.playwright.npmVersion>10.8.2</it.playwright.npmVersion>
+ </properties>
+ <build>
+ <plugins>
+ <!-- Skip the Java failsafe tests in this profile -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- Install Node, run npm install, install browsers, run tests -->
+ <plugin>
+ <groupId>com.github.eirslett</groupId>
+ <artifactId>frontend-maven-plugin</artifactId>
+ <version>1.12.0</version>
+ <configuration>
+ <installDirectory>${project.basedir}/playwright-tests</installDirectory>
+ <workingDirectory>${project.basedir}/playwright-tests</workingDirectory>
+ <npmDownloadRoot>${npm.registry}</npmDownloadRoot>
+ </configuration>
+ <executions>
+ <execution>
+ <id>pw-install-node-and-npm</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>install-node-and-npm</goal>
+ </goals>
+ <configuration>
+ <nodeVersion>${it.playwright.nodeVersion}</nodeVersion>
+ <npmVersion>${it.playwright.npmVersion}</npmVersion>
+ </configuration>
+ </execution>
+ <execution>
+ <id>pw-npm-install</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>npm</goal>
+ </goals>
+ <configuration>
+ <arguments>install</arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>pw-install-browsers</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>npx</goal>
+ </goals>
+ <configuration>
+ <arguments>playwright install chromium</arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>pw-run-tests</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>npx</goal>
+ </goals>
+ <configuration>
+ <arguments>playwright test</arguments>
+ <environmentVariables>
+ <SDC_BASE_URL>${it.playwright.baseUrl}</SDC_BASE_URL>
+ </environmentVariables>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
</project>
public ComponentInstance createNodeOnServiceCanvas(final String serviceName, final String serviceVersion, final String resourceName,
final String resourceVersion) {
- final Point freePositionInCanvas = getFreePositionInCanvas(20);
- final Point pointFromCanvasCenter = calculateOffsetFromCenter(freePositionInCanvas);
- try {
- final Service service =
- AtomicOperationUtils.getServiceObjectByNameAndVersion(DESIGNER, serviceName, serviceVersion);
- final Resource resourceToAdd =
- AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, resourceName, resourceVersion);
- final ComponentInstance componentInstance = AtomicOperationUtils
- .addComponentInstanceToComponentContainer(resourceToAdd, service, DESIGNER, true,
- String.valueOf(pointFromCanvasCenter.getX()), String.valueOf(pointFromCanvasCenter.getY()))
- .left().value();
+ final int maxRetries = 3;
+ Exception lastException = null;
+ for (int attempt = 1; attempt <= maxRetries; attempt++) {
+ final Point freePositionInCanvas = getFreePositionInCanvas(20);
+ final Point pointFromCanvasCenter = calculateOffsetFromCenter(freePositionInCanvas);
+ try {
+ final Service service =
+ AtomicOperationUtils.getServiceObjectByNameAndVersion(DESIGNER, serviceName, serviceVersion);
+ final Resource resourceToAdd =
+ AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, resourceName, resourceVersion);
+ final ComponentInstance componentInstance = AtomicOperationUtils
+ .addComponentInstanceToComponentContainer(resourceToAdd, service, DESIGNER, true,
+ String.valueOf(pointFromCanvasCenter.getX()), String.valueOf(pointFromCanvasCenter.getY()))
+ .left().value();
- LOGGER.debug("Created instance {} in the Service {}", componentInstance.getName(), serviceName);
- return componentInstance;
- } catch (final Exception e) {
- throw new CompositionCanvasRuntimeException("Could not create node through the API", e);
+ LOGGER.debug("Created instance {} in the Service {}", componentInstance.getName(), serviceName);
+ return componentInstance;
+ } catch (final Exception e) {
+ lastException = e;
+ LOGGER.warn("Attempt {}/{} to create node on service canvas failed: {}", attempt, maxRetries, e.getMessage());
+ if (attempt < maxRetries) {
+ try {
+ Thread.sleep(1000L * attempt);
+ } catch (final InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+ }
}
+ throw new CompositionCanvasRuntimeException("Could not create node through the API", lastException);
}
public ComponentInstance createNodeOnResourceCanvas(final String serviceName, final String serviceVersion, final String resourceName,
final String resourceVersion) {
- final Point freePositionInCanvas = getFreePositionInCanvas(20);
- final Point pointFromCanvasCenter = calculateOffsetFromCenter(freePositionInCanvas);
- try {
- final Resource service = AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, serviceName, serviceVersion);
- final Resource resourceToAdd = AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, resourceName, resourceVersion);
- final ComponentInstance componentInstance =
- AtomicOperationUtils.addComponentInstanceToComponentContainer(resourceToAdd, service, DESIGNER, true,
- String.valueOf(pointFromCanvasCenter.getX()), String.valueOf(pointFromCanvasCenter.getY())).left().value();
+ final int maxRetries = 3;
+ Exception lastException = null;
+ for (int attempt = 1; attempt <= maxRetries; attempt++) {
+ final Point freePositionInCanvas = getFreePositionInCanvas(20);
+ final Point pointFromCanvasCenter = calculateOffsetFromCenter(freePositionInCanvas);
+ try {
+ final Resource service = AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, serviceName, serviceVersion);
+ final Resource resourceToAdd = AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, resourceName, resourceVersion);
+ final ComponentInstance componentInstance =
+ AtomicOperationUtils.addComponentInstanceToComponentContainer(resourceToAdd, service, DESIGNER, true,
+ String.valueOf(pointFromCanvasCenter.getX()), String.valueOf(pointFromCanvasCenter.getY())).left().value();
- LOGGER.debug("Created instance {} in the Service {}", componentInstance.getName(), serviceName);
- return componentInstance;
- } catch (final Exception e) {
- throw new CompositionCanvasRuntimeException("Could not create node through the API", e);
+ LOGGER.debug("Created instance {} in the Service {}", componentInstance.getName(), serviceName);
+ return componentInstance;
+ } catch (final Exception e) {
+ lastException = e;
+ LOGGER.warn("Attempt {}/{} to create node on resource canvas failed: {}", attempt, maxRetries, e.getMessage());
+ if (attempt < maxRetries) {
+ try {
+ Thread.sleep(1000L * attempt);
+ } catch (final InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+ }
}
+ throw new CompositionCanvasRuntimeException("Could not create node through the API", lastException);
}
private Point getFreePositionInCanvas(int maxAttempts) {
import lombok.Getter;
import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
import org.openqa.selenium.By;
+import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
public void waitForNotification(final NotificationType notificationType, final int timeout) {
final By messageLocator = getMessageLocator(notificationType);
- final WebElement webElement = waitForElementVisibility(messageLocator, timeout);
- webElement.click();
+ waitForElementVisibility(messageLocator, timeout);
+ clickNotification(messageLocator);
waitForElementInvisibility(messageLocator, 5);
}
+ private void clickNotification(final By messageLocator) {
+ int attempts = 0;
+ while (attempts < 3) {
+ try {
+ final WebElement element = findElement(messageLocator);
+ element.click();
+ return;
+ } catch (final StaleElementReferenceException e) {
+ LOGGER.warn("StaleElementReferenceException on notification click, attempt {}", attempts + 1);
+ attempts++;
+ }
+ }
+ LOGGER.warn("Failed to click notification after {} attempts", attempts);
+ }
+
private By getMessageLocator(final NotificationType notificationType) {
return By.xpath(getMessageXpath(notificationType));
}
<module>onboarding</module>
<module>common-app-logging</module>
<module>common-app-api</module>
- <module>common-be-tests-utils</module>
+ <module>common-be-tests-utils</module>
<module>common-be</module>
<module>catalog-dao</module>
<module>catalog-model</module>
<module>integration-tests</module>
</modules>
</profile>
+ <profile>
+ <id>run-integration-tests-playwright</id>
+ <properties>
+ <skipYamlJsonValidator>true</skipYamlJsonValidator>
+ <checkstyle.skip>true</checkstyle.skip>
+ <surefire.skip.tests>true</surefire.skip.tests>
+ </properties>
+ <modules>
+ <module>onboarding</module>
+ <module>common-app-logging</module>
+ <module>common-app-api</module>
+ <module>common-be-tests-utils</module>
+ <module>common-be</module>
+ <module>catalog-dao</module>
+ <module>catalog-model</module>
+ <module>catalog-be</module>
+ <module>catalog-be-plugins</module>
+ <module>asdctool</module>
+ <module>catalog-ui</module>
+ <module>catalog-fe</module>
+ <module>utils/webseal-simulator</module>
+ <module>integration-tests</module>
+ </modules>
+ </profile>
</profiles>
<repositories>