diff --git a/.gitignore b/.gitignore index 8944d8e57..dec4ef440 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules dist importmap.js .DS_Store +test-results diff --git a/package-lock.json b/package-lock.json index a3f55a7ca..41c9b439c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2015,6 +2015,22 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@playwright/test": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2720,9 +2736,9 @@ } }, "node_modules/@types/dockerode": { - "version": "3.3.45", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.45.tgz", - "integrity": "sha512-iYpZF+xr5QLpIICejLdUF2r5gh8IXY1Gw3WLmt41dUbS3Vn/3hVgL+6lJBVbmrhYBWfbWPPstdr6+A0s95DTWA==", + "version": "3.3.47", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.47.tgz", + "integrity": "sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==", "dev": true, "license": "MIT", "dependencies": { @@ -9632,6 +9648,53 @@ "pathe": "^1.1.2" } }, + "node_modules/playwright": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://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/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -11106,6 +11169,10 @@ "resolved": "packages/roslib-examples", "link": true }, + "node_modules/roslib-test-backend": { + "resolved": "packages/roslib-test-backend", + "link": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -13430,11 +13497,9 @@ "devDependencies": { "@eslint/js": "^9.32.0", "@testing-library/react": "^16.0.0", - "@types/dockerode": "^3.3.45", "@types/node": "^24.0.1", "@types/ws": "^8.5.10", "changelog-maker": "^4.4.5", - "dockerode": "^4.0.9", "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", @@ -13444,6 +13509,7 @@ "jiti": "^2.6.1", "jsdom": "^27.0.0", "jspm": "^4.2.0", + "roslib-test-backend": "^2.0.0", "typedoc": "^0.28.14", "typescript": "^5.9.2", "typescript-eslint": "^8.39.0", @@ -13465,7 +13531,20 @@ "roslib": "^2.0.0" }, "devDependencies": { - "http-server": "^14.1.1" + "@playwright/test": "^1.56.1", + "http-server": "^14.1.1", + "playwright": "^1.56.1" + } + }, + "packages/roslib-test-backend": { + "version": "2.0.0", + "license": "BSD-2-Clause", + "devDependencies": { + "@types/dockerode": "^3.3.47", + "dockerode": "^4.0.9" + }, + "peerDependencies": { + "roslib": "^2.0.0" } } } diff --git a/packages/roslib-examples/package.json b/packages/roslib-examples/package.json index 45a0aaa87..cc0a04bd5 100644 --- a/packages/roslib-examples/package.json +++ b/packages/roslib-examples/package.json @@ -27,10 +27,13 @@ } ], "scripts": { - "start": "http-server .. -o /roslib-examples/src/index.html" + "start": "http-server .. -o /roslib-examples/src/index.html", + "test": "playwright install --with-deps && playwright test" }, "devDependencies": { - "http-server": "^14.1.1" + "@playwright/test": "^1.56.1", + "http-server": "^14.1.1", + "playwright": "^1.56.1" }, "dependencies": { "roslib": "^2.0.0" diff --git a/packages/roslib-examples/playwright.config.ts b/packages/roslib-examples/playwright.config.ts new file mode 100644 index 000000000..ff2362a37 --- /dev/null +++ b/packages/roslib-examples/playwright.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + // Run your local dev server before starting the tests + webServer: { + command: "npm start", + url: "http://localhost:8080", + reuseExistingServer: !process.env.CI, + stdout: "ignore", + stderr: "pipe", + }, +}); diff --git a/packages/roslib-examples/src/action_client.test.ts b/packages/roslib-examples/src/action_client.test.ts new file mode 100644 index 000000000..a7e577c07 --- /dev/null +++ b/packages/roslib-examples/src/action_client.test.ts @@ -0,0 +1,11 @@ +import { test } from "@playwright/test"; + +test("ActionClient example initializes", async ({ page }) => { + test.setTimeout(5000); + await page.goto( + `http://localhost:8080/roslib-examples/src/action_client.html`, + ); + await page.waitForEvent("console", (msg) => + msg.text().includes("Connection made!"), + ); +}); diff --git a/packages/roslib-examples/src/action_server.html b/packages/roslib-examples/src/action_server.html index 906ac0b26..bbb4dd723 100644 --- a/packages/roslib-examples/src/action_server.html +++ b/packages/roslib-examples/src/action_server.html @@ -17,6 +17,10 @@ console.log(error); }); + ros.on("connection", function () { + console.log("Connection made!"); + }); + // The ActionServer // ---------------- diff --git a/packages/roslib-examples/src/action_server.test.ts b/packages/roslib-examples/src/action_server.test.ts new file mode 100644 index 000000000..f782209d6 --- /dev/null +++ b/packages/roslib-examples/src/action_server.test.ts @@ -0,0 +1,11 @@ +import { test } from "@playwright/test"; + +test("SimpleActionServer example initializes", async ({ page }) => { + test.setTimeout(5000); + await page.goto( + `http://localhost:8080/roslib-examples/src/action_server.html`, + ); + await page.waitForEvent("console", (msg) => + msg.text().includes("Connection made!"), + ); +}); diff --git a/packages/roslib-examples/src/ros2_action_client.test.ts b/packages/roslib-examples/src/ros2_action_client.test.ts new file mode 100644 index 000000000..f29bd849a --- /dev/null +++ b/packages/roslib-examples/src/ros2_action_client.test.ts @@ -0,0 +1,11 @@ +import { test } from "@playwright/test"; + +test("ROS 2 Action client example initializes", async ({ page }) => { + test.setTimeout(5000); + await page.goto( + `http://localhost:8080/roslib-examples/src/ros2_action_client.html`, + ); + await page.waitForEvent("console", (msg) => + msg.text().includes("Connection made!"), + ); +}); diff --git a/packages/roslib-examples/src/ros2_action_server.html b/packages/roslib-examples/src/ros2_action_server.html index 17cb158d6..8cfec4b51 100644 --- a/packages/roslib-examples/src/ros2_action_server.html +++ b/packages/roslib-examples/src/ros2_action_server.html @@ -12,6 +12,10 @@ url: "ws://localhost:9090", }); + ros.on("connection", function () { + console.log("Connection made!"); + }); + // If there is an error on the backend, an 'error' emit will be emitted. ros.on("error", function (error) { console.log(error); diff --git a/packages/roslib-examples/src/ros2_action_server.test.ts b/packages/roslib-examples/src/ros2_action_server.test.ts new file mode 100644 index 000000000..10c5fe637 --- /dev/null +++ b/packages/roslib-examples/src/ros2_action_server.test.ts @@ -0,0 +1,11 @@ +import { test } from "@playwright/test"; + +test("ROS 2 Action server example initializes", async ({ page }) => { + test.setTimeout(5000); + await page.goto( + `http://localhost:8080/roslib-examples/src/ros2_action_server.html`, + ); + await page.waitForEvent("console", (msg) => + msg.text().includes("Connection made!"), + ); +}); diff --git a/packages/roslib-examples/src/ros2_simple.test.ts b/packages/roslib-examples/src/ros2_simple.test.ts new file mode 100644 index 000000000..13e6f7f15 --- /dev/null +++ b/packages/roslib-examples/src/ros2_simple.test.ts @@ -0,0 +1,9 @@ +import { test } from "@playwright/test"; + +test("ROS 2 simple example initializes", async ({ page }) => { + test.setTimeout(5000); + await page.goto(`http://localhost:8080/roslib-examples/src/ros2_simple.html`); + await page.waitForEvent("console", (msg) => + msg.text().includes("Connection made!"), + ); +}); diff --git a/packages/roslib-examples/src/simple.test.ts b/packages/roslib-examples/src/simple.test.ts new file mode 100644 index 000000000..5084e5d6f --- /dev/null +++ b/packages/roslib-examples/src/simple.test.ts @@ -0,0 +1,9 @@ +import { test } from "@playwright/test"; + +test("Simple example initializes", async ({ page }) => { + test.setTimeout(5000); + await page.goto(`http://localhost:8080/roslib-examples/src/simple.html`); + await page.waitForEvent("console", (msg) => + msg.text().includes("Connection made!"), + ); +}); diff --git a/packages/roslib-test-backend/package.json b/packages/roslib-test-backend/package.json new file mode 100644 index 000000000..b4e61ba93 --- /dev/null +++ b/packages/roslib-test-backend/package.json @@ -0,0 +1,31 @@ +{ + "name": "roslib-test-backend", + "version": "2.0.0", + "description": "Integration test backend for roslib", + "homepage": "https://github.com/RobotWebTools/roslibjs#readme", + "bugs": { + "url": "https://github.com/RobotWebTools/roslibjs/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/RobotWebTools/roslibjs.git" + }, + "license": "BSD-2-Clause", + "author": "Robot Webtools Team (https://robotwebtools.github.io)", + "contributors": [ + { + "name": "Ezra Brooks", + "email": "ezra@brooks.cx", + "url": "https://github.com/EzraBrooks" + } + ], + "type": "module", + "main": "ros-backend.ts", + "peerDependencies": { + "roslib": "^2.0.0" + }, + "devDependencies": { + "@types/dockerode": "^3.3.47", + "dockerode": "^4.0.9" + } +} diff --git a/packages/roslib/test/setup/ros-backend.ts b/packages/roslib-test-backend/ros-backend.ts similarity index 99% rename from packages/roslib/test/setup/ros-backend.ts rename to packages/roslib-test-backend/ros-backend.ts index 5eb137344..a066ee245 100644 --- a/packages/roslib/test/setup/ros-backend.ts +++ b/packages/roslib-test-backend/ros-backend.ts @@ -4,7 +4,7 @@ */ import Docker from "dockerode"; -import Ros from "../../src/core/Ros.ts"; +import { Ros } from "roslib"; const CONTAINER_NAME = "roslibjs-test-backend"; const CONTAINER_PORT = 9090; diff --git a/packages/roslib/test/examples/setup_examples.launch b/packages/roslib-test-backend/setup_examples.launch similarity index 100% rename from packages/roslib/test/examples/setup_examples.launch rename to packages/roslib-test-backend/setup_examples.launch diff --git a/packages/roslib/test/examples/setup_examples_ros2.launch.xml b/packages/roslib-test-backend/setup_examples_ros2.launch.xml similarity index 100% rename from packages/roslib/test/examples/setup_examples_ros2.launch.xml rename to packages/roslib-test-backend/setup_examples_ros2.launch.xml diff --git a/packages/roslib/package.json b/packages/roslib/package.json index 5419db636..c4f2562e6 100644 --- a/packages/roslib/package.json +++ b/packages/roslib/package.json @@ -18,11 +18,9 @@ "devDependencies": { "@eslint/js": "^9.32.0", "@testing-library/react": "^16.0.0", - "@types/dockerode": "^3.3.45", "@types/node": "^24.0.1", "@types/ws": "^8.5.10", "changelog-maker": "^4.4.5", - "dockerode": "^4.0.9", "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", @@ -32,6 +30,7 @@ "jiti": "^2.6.1", "jsdom": "^27.0.0", "jspm": "^4.2.0", + "roslib-test-backend": "^2.0.0", "typedoc": "^0.28.14", "typescript": "^5.9.2", "typescript-eslint": "^8.39.0", diff --git a/packages/roslib/test/setup/vitest-setup.ts b/packages/roslib/test/setup/vitest-setup.ts index 671ba9cd9..8dc619019 100644 --- a/packages/roslib/test/setup/vitest-setup.ts +++ b/packages/roslib/test/setup/vitest-setup.ts @@ -3,7 +3,7 @@ * This file sets up and tears down the ROS backend container for all tests */ -import { setupBackend, teardownBackend } from "./ros-backend.ts"; +import { setupBackend, teardownBackend } from "roslib-test-backend"; // Global setup - runs once before all tests export async function setup() {