Skip to content
Closed
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
18 changes: 15 additions & 3 deletions .github/workflows/_integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@
timeout-minutes: 60

env:
CODE_VERSION: max
# Pinned instead of `max`: VS Code >= 1.123 ships Chromium 148, which duplicates every
# 256 KiB chunk of a webview resource while chromedriver is attached. The Kaoto bundle
# then fails to parse and the editor renders blank. See KaotoIO/vscode-kaoto#1468.
# 1.122.0 is the newest release still on Chromium 142. Revert to `max` once fixed upstream.
CODE_VERSION: '1.122.0'
TEST_RESOURCES: test-resources

steps:
Expand All @@ -83,7 +87,7 @@
enable-qemu: ${{ inputs.enable-qemu && 'true' || 'false' }}
- name: Install Yarn
if: ${{ inputs.install-yarn }}
run: npm install -g yarn

Check warning on line 90 in .github/workflows/_integration-tests.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=KaotoIO_vscode-kaoto&issues=AZ_v-Q0RKVhGG-ux4FiJ&open=AZ_v-Q0RKVhGG-ux4FiJ&pullRequest=1465

Check warning on line 90 in .github/workflows/_integration-tests.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--ignore-scripts" allows lifecycle scripts to run during package installation.

See more on https://sonarcloud.io/project/issues?id=KaotoIO_vscode-kaoto&issues=AZ_v-Q0RKVhGG-ux4FiI&open=AZ_v-Q0RKVhGG-ux4FiI&pullRequest=1465
- name: Download VSIX artifact
uses: actions/download-artifact@v8
with:
Expand Down Expand Up @@ -112,7 +116,11 @@
timeout-minutes: 60

env:
CODE_VERSION: max
# Pinned instead of `max`: VS Code >= 1.123 ships Chromium 148, which duplicates every
# 256 KiB chunk of a webview resource while chromedriver is attached. The Kaoto bundle
# then fails to parse and the editor renders blank. See KaotoIO/vscode-kaoto#1468.
# 1.122.0 is the newest release still on Chromium 142. Revert to `max` once fixed upstream.
CODE_VERSION: '1.122.0'
TEST_RESOURCES: test-resources

steps:
Expand All @@ -122,7 +130,7 @@
cache: yarn
- name: Install Yarn
if: ${{ inputs.install-yarn }}
run: npm install -g yarn

Check warning on line 133 in .github/workflows/_integration-tests.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--ignore-scripts" allows lifecycle scripts to run during package installation.

See more on https://sonarcloud.io/project/issues?id=KaotoIO_vscode-kaoto&issues=AZ_v-Q0RKVhGG-ux4FiK&open=AZ_v-Q0RKVhGG-ux4FiK&pullRequest=1465

Check warning on line 133 in .github/workflows/_integration-tests.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=KaotoIO_vscode-kaoto&issues=AZ_v-Q0RKVhGG-ux4FiL&open=AZ_v-Q0RKVhGG-ux4FiL&pullRequest=1465
- name: Download VSIX artifact
uses: actions/download-artifact@v8
with:
Expand All @@ -148,7 +156,11 @@
timeout-minutes: 60

env:
CODE_VERSION: max
# Pinned instead of `max`: VS Code >= 1.123 ships Chromium 148, which duplicates every
# 256 KiB chunk of a webview resource while chromedriver is attached. The Kaoto bundle
# then fails to parse and the editor renders blank. See KaotoIO/vscode-kaoto#1468.
# 1.122.0 is the newest release still on Chromium 142. Revert to `max` once fixed upstream.
CODE_VERSION: '1.122.0'
TEST_RESOURCES: test-resources

steps:
Expand All @@ -158,7 +170,7 @@
cache: yarn
- name: Install Yarn
if: ${{ inputs.install-yarn }}
run: npm install -g yarn

Check warning on line 173 in .github/workflows/_integration-tests.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--ignore-scripts" allows lifecycle scripts to run during package installation.

See more on https://sonarcloud.io/project/issues?id=KaotoIO_vscode-kaoto&issues=AZ_v-Q0RKVhGG-ux4FiM&open=AZ_v-Q0RKVhGG-ux4FiM&pullRequest=1465

Check warning on line 173 in .github/workflows/_integration-tests.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=KaotoIO_vscode-kaoto&issues=AZ_v-Q0RKVhGG-ux4FiN&open=AZ_v-Q0RKVhGG-ux4FiN&pullRequest=1465
- name: Download VSIX artifact
uses: actions/download-artifact@v8
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ yarn-error.log*
.camel-jbang
.camel-jbang-run
.citrus-jbang

# AI agent delegation run logs
.bob-runs
17 changes: 17 additions & 0 deletions extester.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "./node_modules/vscode-extension-tester/resources/extester.schema.json",
"setup": {
"extensionsDir": "./test-resources",
"packageOptions": {
"useYarn": true,
"dependencies": false
}
},
"run": {
"testFiles": ["out/**/*.test.js"],
"resources": ["./test Fixture with speci@l chars"],
"extensionsDir": "./test-resources",
"settings": "./it-tests/vscode-settings.json",
"customPageObjects": "./out/pageObjects/locators.js"
}
}
139 changes: 32 additions & 107 deletions it-tests/BasicFlow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { By, EditorView, until, VSBrowser, WebDriver, WebView, logging, InputBox } from 'vscode-extension-tester';
import { By, EditorView, until, VSBrowser, WebDriver, WebView, logging } from 'vscode-extension-tester';
import { assert } from 'chai';
import * as path from 'path';
import {
Expand All @@ -23,6 +23,7 @@ import {
openResourcesAndWaitForActivation,
workaroundToRedrawContextualMenu,
} from './Util';
import { KaotoCanvas, KaotoEditor, kaotoLocators, DataMapperEditor } from './pageObjects';
import { waitUntil } from 'async-wait-until';
import * as fs from 'fs-extra';

Expand Down Expand Up @@ -132,7 +133,7 @@ describe('Kaoto basic development flow', function () {
// TODO: Add a target xsd
// TODO: Map an element

await (await driver.findElement(By.css('a[data-testid="design-tab"]'))).click();
await KaotoEditor.clickDesignTab(driver);

const files = fs.readdirSync(workspaceFolder);
const xslFiles = files.filter((file) => file.endsWith('.xsl'));
Expand Down Expand Up @@ -175,90 +176,27 @@ describe('Kaoto basic development flow', function () {
});

async function addXsdForSource(driver: WebDriver, kaotoWebview: WebView) {
await driver.wait(
until.elementLocated(By.css('button[data-testid="attach-schema-sourceBody-Body-button"]')),
5000,
'Cannot find the button to attach the schema',
);
await (await driver.findElement(By.css('button[data-testid="attach-schema-sourceBody-Body-button"]'))).click();

await waitForXsdAttachModal(driver);
const schemaButton = await driver.findElement(By.xpath("//button[@data-testid='attach-schema-modal-btn-file']"));
await schemaButton.click();

await kaotoWebview.switchBack();
const xsdInputbox = await InputBox.create(10000);
await xsdInputbox.setText('shiporder.xsd');
try {
const checkboxes = await xsdInputbox.getCheckboxes();
if (checkboxes.length > 0) {
await checkboxes[0].select();
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (!/NoSuchElement/i.test(message) && !/checkbox/i.test(message)) {
throw error;
}
// Fallback for Kaoto <2.10.0 (no checkboxes)
}
await xsdInputbox.confirm();
await kaotoWebview.switchToFrame();

await waitForXsdAttachModal(driver);
const attachButton = await driver.findElement(By.xpath("//button[@data-testid='attach-schema-modal-btn-attach']"));
await attachButton.click();

await driver.wait(
until.elementLocated(
// Kaoto 2.6- using -field-, Kaoto 2.7+ using -fx-
By.xpath('//div[starts-with(@data-testid, "node-source-field-shiporder-")] | //div[starts-with(@data-testid, "node-source-fx-shiporder-")]'),
),
5000,
'Root of the imported xsd is not displayed in the UI',
);
}

async function waitForXsdAttachModal(driver: WebDriver, timeout: number = 3_000): Promise<void> {
await driver.wait(until.elementLocated(By.xpath("//div[@data-testid='attach-schema-modal']")), timeout, 'Cannot find XSD schema modal dialog');
await DataMapperEditor.attachXsdSchema(driver, kaotoWebview, 'sourceBody-Body', 'shiporder.xsd');
await DataMapperEditor.waitForSourceField(driver, 5_000);
}

async function openDataMapperEditor(driver: WebDriver) {
const kaotoNode = await driver.findElement(
By.css(
`g[data-testid^="custom-node__kaoto-datamapper"],g[data-testid="custom-node__route.from.steps.0.kaoto-datamapper"],g[data-testid="${DATA_TEST_ID_DATAMAPPERSTEP_2_5}"]`,
),
);
await kaotoNode.click();
await driver.wait(
until.elementLocated(By.css('button[title="Click to launch the Kaoto DataMapper editor"]')),
5000,
'Cannot find the button to open the datamapper',
);
await (await driver.findElement(By.css('button[title="Click to launch the Kaoto DataMapper editor"]'))).click();
const nodeSelector = `g[data-testid^="custom-node__kaoto-datamapper"],g[data-testid="custom-node__route.from.steps.0.kaoto-datamapper"],g[data-testid="${DATA_TEST_ID_DATAMAPPERSTEP_2_5}"]`;
await DataMapperEditor.openFromNode(driver, nodeSelector);
}

async function deleteDataMapperStep(driver: WebDriver, workspaceFolder: string, kaotoWebview: WebView) {
await checkStepWithTestIdOrNodeLabelPresent(driver, 'custom-node__kaoto-datamapper', 'kaoto-datamapper');

const kaotoNodeConfigured = await driver.findElement(
By.xpath(`//*[name()='g' and starts-with(@data-testid,'custom-node__kaoto-datamapper') or @data-nodelabel='kaoto-datamapper']`),
);
const kaotoNodeConfigured = await KaotoCanvas.findNodeByTestIdOrLabel(driver, 'custom-node__kaoto-datamapper', 'kaoto-datamapper');
await kaotoNodeConfigured.click();

await workaroundToRedrawContextualMenu(kaotoWebview);

await driver.wait(until.elementLocated(By.css("button[id='reset-view']")), 5000, 'Cannot find the reset view button');
await (await driver.findElement(By.css("button[id='reset-view']"))).click(); // reset view nodes position to see the delete button

await driver.wait(until.elementLocated(By.xpath("//div[@data-testid='step-toolbar']")), 5000, 'Cannot find the step toolbar');
await (await driver.findElement(By.css('button[data-testid="kaoto-datamapper|step-toolbar-button-delete"]'))).click();
await KaotoCanvas.resetView(driver, 5_000);
await KaotoCanvas.deleteStep(driver, 'kaoto-datamapper', 5_000);
await KaotoCanvas.confirmDeleteStepAndFile(driver, 5_000);

await driver.wait(
until.elementLocated(By.xpath("//div[@data-ouia-component-id='ActionConfirmationModal']")),
5000,
'Cannot find the modal to delete the data mapper step',
);
await (await driver.findElement(By.css('button[data-testid="action-confirmation-modal-btn-del-step-and-file"]'))).click();
await driver.wait(
async () => {
const filesAfterDeletion = fs.readdirSync(workspaceFolder);
Expand All @@ -271,63 +209,50 @@ async function deleteDataMapperStep(driver: WebDriver, workspaceFolder: string,
}

async function createNewRoute(driver: WebDriver) {
await (await driver.findElement(By.xpath("//button[@data-testid='dsl-list-btn']"))).click();
await KaotoCanvas.clickDslListButton(driver);
}

async function addAMQPStep(driver: WebDriver) {
const canvasNode = await KaotoCanvas.findTimerOrFromNode(driver);
await KaotoCanvas.openContextMenu(driver, canvasNode);
await KaotoCanvas.clickReplaceInContextMenu(driver);

await driver.wait(
until.elementLocated(By.css('g[data-testid^="custom-node__timer"],g[data-testid="custom-node__route.from"]')),
until.elementLocated(By.xpath(kaotoLocators.KaotoCatalog.tileHeaderByTestId('amqp'))),
5000,
'Cannot find the node for the timer',
'Cannot find the tile header for the AMQP step',
);

const canvasNode = await driver.findElement(By.css('g[data-testid^="custom-node__timer"],g[data-testid="custom-node__route.from"]'));
await driver.actions().contextClick(canvasNode).perform();

await driver.wait(until.elementLocated(By.className('pf-topology-context-menu__c-dropdown__menu')), 5000, 'Cannot find the context menu');
await (await driver.findElement(By.xpath("//\*[@data-testid='context-menu-item-replace']"))).click();

await driver.wait(until.elementLocated(By.xpath("//div[@data-testid='tile-header-amqp']")), 5000, 'Cannot find the tile header for the AMQP step');
await (await driver.findElement(By.xpath("//div[@data-testid='tile-header-amqp']"))).click();
await (await driver.findElement(By.xpath(kaotoLocators.KaotoCatalog.tileHeaderByTestId('amqp')))).click();
}

async function addDatamapperStep(driver: WebDriver, kaotoWebview: WebView) {
await driver.wait(
until.elementLocated(By.css('g[data-testid^="custom-node__log"],g[data-testid="custom-node__route.from.steps.0.log"]')),
5000,
'Cannot find the node for the log',
);

const canvasNode = await driver.findElement(By.css('g[data-testid^="custom-node__log"],g[data-testid="custom-node__route.from.steps.0.log"]'));
await driver.actions().contextClick(canvasNode).perform();
const canvasNode = await KaotoCanvas.findLogNode(driver);
await KaotoCanvas.openContextMenu(driver, canvasNode);

await workaroundToRedrawContextualMenu(kaotoWebview);

await driver.wait(until.elementLocated(By.className('pf-topology-context-menu__c-dropdown__menu')), 5000, 'Cannot find the context menu');
await (await driver.findElement(By.xpath("//*[@data-testid='context-menu-item-replace']"))).click();
await KaotoCanvas.clickReplaceInContextMenu(driver);

await driver.wait(until.elementLocated(By.xpath("//input[@placeholder='Filter by name, description or tag']")), 5000, 'Cannot find the filter input');
const filterInput = await driver.findElement(By.xpath("//input[@placeholder='Filter by name, description or tag']"));
await driver.wait(until.elementLocated(By.xpath(kaotoLocators.KaotoCatalog.filterInput)), 5000, 'Cannot find the filter input');
const filterInput = await driver.findElement(By.xpath(kaotoLocators.KaotoCatalog.filterInput));
await filterInput.sendKeys('datamapper');
await driver.wait(until.elementLocated(By.xpath("//div[@data-testid='tile-kaoto-datamapper']")), 5000, 'Cannot find the tile for the DataMapper step');

await (await driver.findElement(By.xpath("//div[@data-testid='tile-kaoto-datamapper']"))).click();
await driver.wait(
until.elementLocated(By.xpath(kaotoLocators.KaotoCatalog.tileHeaderByTestId('kaoto-datamapper'))),
5000,
'Cannot find the tile for the DataMapper step',
);
await (await driver.findElement(By.xpath(kaotoLocators.KaotoCatalog.tileHeaderByTestId('kaoto-datamapper')))).click();
}

/**
*
* @param driver
* @param testId used for Kaoto 2.3
* @param nodeLabel used for Kaoto 2.4
*/
async function checkStepWithTestIdOrNodeLabelPresent(driver: WebDriver, testId: string, nodeLabel: string) {
await driver.wait(until.elementLocated(By.xpath(`//*[name()='g' and starts-with(@data-testid,'${testId}') or @data-nodelabel='${nodeLabel}']`)), 5_000);
await KaotoCanvas.findNodeByTestIdOrLabel(driver, testId, nodeLabel);
}

async function checkIntegrationNameInTopBarLoaded(driver: WebDriver, name: string) {
await driver.wait(
until.elementLocated(By.xpath(`//span[@data-testid='flows-list-route-id' and contains(., '${name}')]`)),
5_000,
`Unable to locate integration name '${name} in top bar!'`,
);
await KaotoEditor.waitForIntegrationName(driver, name);
}
43 changes: 17 additions & 26 deletions it-tests/MavenDependencyUpdate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
import {
By,
EditorView,
until,
VSBrowser,
WebDriver,
Workbench,
Expand All @@ -38,6 +37,7 @@ import {
CheckboxSetting,
before,
} from 'vscode-extension-tester';
import { KaotoCanvas } from './pageObjects';
import { assert, expect } from 'chai';
import * as fs from 'fs';

Expand Down Expand Up @@ -131,9 +131,14 @@ describe('Maven dependency update pom.xml', function () {

const notificationsCenter = await new Workbench().openNotificationsCenter();
const notifications = await notificationsCenter.getNotifications(NotificationType.Warning);
const camelDependenciesUpdateNotification = notifications.find(async (notification) =>
(await notification.getMessage()).includes('The pom.xml file has unsaved changes. Please save it before updating Camel dependencies.'),
);
let camelDependenciesUpdateNotification;
for (const notification of notifications) {
const message = await notification.getMessage();
if (message.includes('The pom.xml file has unsaved changes. Please save it before updating Camel dependencies.')) {
camelDependenciesUpdateNotification = notification;
break;
}
}
await camelDependenciesUpdateNotification?.takeAction('Save and Continue');

await waitForNotifications();
Expand Down Expand Up @@ -250,26 +255,13 @@ describe('Maven dependency update pom.xml', function () {
* Add the SQL component step to the topology.
*/
async function addSqlComponentStep() {
// hover over edge to show add step button
const setBodyToLogEdge = await driver.findElement(By.css('g[data-id^="my-camel-quarkus-route|route.from.steps.0.setBody >>> route.from.steps.1.log"]'));
await driver.actions().move({ origin: setBodyToLogEdge, duration: 2_000 }).perform();

// click Add Step button
const addStepButton = await setBodyToLogEdge.findElement(By.className('custom-edge__add-step add-step-icon'));
// hover over edge to show add step button and click Add Step button
const edgeSelector = 'g[data-id^="my-camel-quarkus-route|route.from.steps.0.setBody >>> route.from.steps.1.log"]';
const addStepButton = await KaotoCanvas.getAddStepButton(driver, edgeSelector);
await addStepButton.click();

// add text into catalog filter
await driver.wait(until.elementLocated(By.className('pf-v6-c-text-input-group__text-input')), 5_000);
const textInput = await driver.findElement(By.className('pf-v6-c-text-input-group__text-input'));
await textInput.click();
await textInput.clear();
await textInput.sendKeys('sql');

await driver.wait(until.elementLocated(By.css('div[data-testid="tile-sql"]')), 5_000);

// select SQL component from catalog
const sqlComponent = await driver.findElement(By.css('div[data-testid="tile-sql"]'));
await sqlComponent.click();
// filter catalog and select SQL component
await KaotoCanvas.filterCatalogAndSelectTile(driver, 'sql', 'sql');
}

/**
Expand All @@ -282,14 +274,13 @@ describe('Maven dependency update pom.xml', function () {
).kaotoWebview;

// right click on SQL component node
const sqlComponent = await driver.findElement(By.css('g[data-nodelabel="sql"]'));
await driver.actions().contextClick(sqlComponent).perform();
const sqlNode = await driver.findElement(By.css('g[data-nodelabel="sql"]'));
await KaotoCanvas.openContextMenu(driver, sqlNode);

await workaroundToRedrawContextualMenu(kaotoWebview);

// click Delete button
const deleteButton = await driver.findElement(By.css('li[data-testid="context-menu-item-delete"]'));
await deleteButton.click();
await KaotoCanvas.clickDeleteInContextMenu(driver);

// save editor
await kaotoWebview.switchBack();
Expand Down
Loading
Loading