diff --git a/pyproject.toml b/pyproject.toml index c887dd16..85214b99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -202,4 +202,4 @@ plugins = "sqlalchemy.ext.mypy.plugin" skyvern = "skyvern.cli.commands:cli_app" [tool.pytest.ini_options] -norecursedirs = ["eval"] +norecursedirs = ["eval", "tests/sdk"] diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 00000000..e5f5395e --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +asyncio_mode = auto +addopts = --capture=tee-sys diff --git a/tests/sdk/README.md b/tests/sdk/README.md new file mode 100644 index 00000000..e714cf2d --- /dev/null +++ b/tests/sdk/README.md @@ -0,0 +1,59 @@ +# Skyvern SDK Tests + +Test suite for Skyvern Python and TypeScript SDKs with shared HTML fixtures in `web/`. + +## Python SDK + +**Location:** `tests/sdk/python_sdk/` + +### Prerequisites +- Requires `.env` with `SKYVERN_API_KEY` +- Browser fixture auto-launches on port 9222 +- Web server fixture auto-starts on port 9010 + +### Running Tests + +```bash +# Run all tests +pytest tests/sdk/python_sdk/ + +# Run specific test file +pytest tests/sdk/python_sdk/test_sdk_simple_actions.py + +# Run specific test +pytest tests/sdk/python_sdk/test_sdk_simple_actions.py::test_clicks +``` + +--- + +## TypeScript SDK + +**Location:** `tests/sdk/typescript_sdk/` + +### Prerequisites +- Requires `.env` with `SKYVERN_API_KEY` +- Requires Chrome with CDP on `localhost:9222` +- Web server auto-starts via `run-test.js` + +**Launch Chrome with CDP:** +```bash +/Users/stas/Library/Caches/ms-playwright/chromium-1194/chrome-mac/Chromium.app/Contents/MacOS/Chromium \ + --remote-debugging-port=9222 \ + --user-data-dir=~/tmp/chrome-playwright \ + about:blank +``` + +### Running Tests + +```bash +cd tests/sdk/typescript_sdk + +# First time setup +npm install + +# Run specific test +npm test test_simple_actions.ts testClicks + +# Run all tests in a file +npm test test_simple_actions.ts all +``` diff --git a/tests/sdk/python_sdk/.gitignore b/tests/sdk/python_sdk/.gitignore new file mode 100644 index 00000000..8b693a96 --- /dev/null +++ b/tests/sdk/python_sdk/.gitignore @@ -0,0 +1,2 @@ +.env +*.png \ No newline at end of file diff --git a/tests/sdk/python_sdk/conftest.py b/tests/sdk/python_sdk/conftest.py new file mode 100644 index 00000000..3e102a0e --- /dev/null +++ b/tests/sdk/python_sdk/conftest.py @@ -0,0 +1,99 @@ +import asyncio +import socket +import threading +import time +from http.server import HTTPServer, SimpleHTTPRequestHandler +from pathlib import Path + +import pytest + +from skyvern import Skyvern + + +@pytest.fixture(scope="session") +def event_loop(): + """Create a session-scoped event loop for async session fixtures.""" + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +class QuietHTTPRequestHandler(SimpleHTTPRequestHandler): + """HTTP request handler that suppresses log messages.""" + + def log_message(self, format, *args): + """Override to suppress HTTP request logging.""" + + +def _wait_for_server(host: str, port: int, timeout: float = 5.0) -> bool: + """Wait for the server to be ready by attempting to connect.""" + start_time = time.time() + deadline = start_time + timeout + + while time.time() < deadline: + try: + with socket.create_connection((host, port), timeout=0.1): + return True + except (ConnectionRefusedError, OSError): + time.sleep(0.05) + + return False + + +@pytest.fixture(scope="session") +async def web_server(): + """ + Start a local HTTP server on port 9009 serving from the 'web' directory. + + This is an async fixture that properly waits for the server to be ready. + """ + # Get the directory where this conftest file is located + test_dir = Path(__file__).parent + web_dir = test_dir.parent / "web" + + # Create web directory if it doesn't exist + web_dir.mkdir(exist_ok=True) + + # Create a handler class that serves from the specific directory + def handler_factory(*args, **kwargs): + return QuietHTTPRequestHandler(*args, directory=str(web_dir), **kwargs) + + # Create and start the HTTP server + server = HTTPServer(("localhost", 9009), handler_factory) + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + + # Wait for the server to be ready (async-friendly) + await asyncio.sleep(0.1) + + # Verify server is responding + if not _wait_for_server("localhost", 9009): + # Don't wait forever - just close the socket and let daemon thread die + server.server_close() + raise RuntimeError("Server failed to start") + + base_url = "http://localhost:9009" + yield base_url + + # Cleanup: Don't call shutdown() as it can block forever waiting for active connections + # Instead, just close the socket and let the daemon thread die + server.server_close() + + +@pytest.fixture(scope="session") +async def skyvern_browser(): + """ + Launch a local browser once for the entire test session and reuse it across tests. + + This ensures all tests use the same browser instance, avoiding connection issues. + """ + skyvern = Skyvern.local() + browser = await skyvern.launch_local_browser(headless=False) + + yield browser + + # Cleanup: close the browser after all tests complete + try: + await browser.close() + except Exception: + pass # Ignore cleanup errors diff --git a/tests/sdk/python_sdk/test_sdk_run_tasks.py b/tests/sdk/python_sdk/test_sdk_run_tasks.py new file mode 100644 index 00000000..8750f5ea --- /dev/null +++ b/tests/sdk/python_sdk/test_sdk_run_tasks.py @@ -0,0 +1,74 @@ +import asyncio + +import pytest + +from skyvern.forge.sdk.schemas.credentials import NonEmptyPasswordCredential, TotpType +from skyvern.schemas.run_blocks import CredentialType + + +@pytest.mark.asyncio +async def test_login(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/login.html") + + credentials = await skyvern_browser.skyvern.get_credentials() + credential = next((item for item in credentials if item.name == "test_login"), None) + if credential is None: + print("Credentials not found. Creating new one.") + credential = await skyvern_browser.skyvern.create_credential( + name="test_login", + credential_type="password", + credential=NonEmptyPasswordCredential( + username="testlogin", + password="testpassword", + totp=None, + totp_type=TotpType.NONE, + ), + ) + + await page.agent.login( + credential_type=CredentialType.skyvern, + credential_id=credential.credential_id, + ) + + await page.click("#accountBtn") + await asyncio.sleep(1) + await page.act("Click on 'Click Me' button") + assert await page.locator("#clickCounter").text_content() == "Button clicked 1 times" + + await asyncio.sleep(1) + await page.screenshot(path="screenshot.png", full_page=True) + + +@pytest.mark.asyncio +async def test_test_finishes_login(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto("https://www.saucedemo.com/") + await page.fill("#user-name", "standard_user") + await page.fill("#password", "secret_sauce") + + await page.agent.run_task("Click on login button", engine="skyvern-1.0") + + assert await page.get_by_role("button", name="Add to cart").count() > 0 + + +@pytest.mark.asyncio +async def test_download_file(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/download_file.html") + + r = await page.agent.download_files( + prompt="Click the 'Download PDF Report' button to download the sample PDF file", + download_suffix="sample_report.pdf", + ) + for downloaded_file in r.downloaded_files: + print(downloaded_file) + assert len(r.downloaded_files) == 1 + + await asyncio.sleep(2) + await page.screenshot(path="download_test.png", full_page=True) + + assert len(r.downloaded_files) == 1 diff --git a/tests/sdk/python_sdk/test_sdk_simple_actions.py b/tests/sdk/python_sdk/test_sdk_simple_actions.py new file mode 100644 index 00000000..4449858f --- /dev/null +++ b/tests/sdk/python_sdk/test_sdk_simple_actions.py @@ -0,0 +1,235 @@ +import pytest + + +@pytest.mark.asyncio +async def test_clicks(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/click.html") + + assert await page.locator("#counter").text_content() == "Button clicked 0 times" + await page.click("#button") + assert await page.locator("#counter").text_content() == "Button clicked 1 times" + + await page.click(prompt="Click on the button") + assert await page.locator("#counter").text_content() == "Button clicked 2 times" + + print("Fallback") + await page.click("#counterBroken", prompt="Click on the button") + assert await page.locator("#counter").text_content() == "Button clicked 3 times" + + await page.click("#button") + assert await page.locator("#counter").text_content() == "Button clicked 4 times" + print("All tests passed") + + +@pytest.mark.asyncio +async def test_fill(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/input.html") + + assert await page.locator("#output").text_content() == "" + await page.fill("#nameInput", "Test1") + await page.click("#submitBtn") + assert await page.locator("#output").text_content() == "Hello, Test1!" + + await page.fill(prompt="Type 'Test2' in the name input") + await page.click("#submitBtn") + assert await page.locator("#output").text_content() == "Hello, Test2!" + + await page.fill(prompt="Type the value in the name input", value="Test3") + await page.click("#submitBtn") + assert await page.locator("#output").text_content() == "Hello, Test3!" + + # fallback + await page.fill("#nameInputBroken", "TestFallback", prompt="Fill the name input") + await page.click("#submitBtn") + assert await page.locator("#output").text_content() == "Hello, TestFallback!" + + +@pytest.mark.asyncio +async def test_select_option(web_server, skyvern_browser): + """Test using page.act() with natural language prompts on a combobox.""" + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/combobox.html") + + await page.select_option("#cars", "audi") + assert await page.locator("#cars").input_value() == "audi" + + await page.select_option("#cars", value="opel") + assert await page.locator("#cars").input_value() == "opel" + + await page.select_option("#cars", label="Saab") + assert await page.locator("#cars").input_value() == "saab" + + await page.select_option(prompt="Select 'Audi' i the car combobox") + assert await page.locator("#cars").input_value() == "audi" + + # fallback + await page.select_option("#cars-broken", "opel", prompt="Select 'Opel' i the car combobox") + assert await page.locator("#cars").input_value() == "opel" + + await page.select_option("#cars-broken", label="Saab", prompt="Select 'Saab' i the car combobox") + assert await page.locator("#cars").input_value() == "saab" + + +@pytest.mark.asyncio +async def test_act_combobox(web_server, skyvern_browser): + """Test using page.act() with natural language prompts on a combobox.""" + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/combobox.html") + + await page.act("select 'Audi' from the combobox") + + assert await page.locator("#cars").input_value() == "audi" + + +@pytest.mark.asyncio +async def test_act_input_and_click(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/input.html") + + await page.act("type 'ActTest' into the input box") + await page.act("click on the button") + + assert await page.locator("#output").text_content() == "Hello, ActTest!" + + +@pytest.mark.asyncio +async def test_upload(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + image_url = "https://img.freepik.com/free-photo/portrait-beautiful-purebred-pussycat-with-shorthair-orange-collar-neck-sitting-floor-reacting-camera-flash-scared-looking-light-indoor_8353-12551.jpg?semt=ais_hybrid&w=740&q=80" + + await page.goto(f"{web_server}/upload.html") + + await page.reload() + assert (await page.locator("#uploadedImage").get_attribute("src")).__str__() == "" + await page.upload_file("#imageUpload", image_url) + assert (await page.locator("#uploadedImage").get_attribute("src")).__str__().startswith("data:image/jpeg") + + await page.reload() + assert (await page.locator("#uploadedImage").get_attribute("src")).__str__() == "" + await page.upload_file(prompt="Upload this", files=image_url) + assert (await page.locator("#uploadedImage").get_attribute("src")).__str__().startswith("data:image/jpeg") + + await page.reload() + assert (await page.locator("#uploadedImage").get_attribute("src")).__str__() == "" + await page.upload_file(prompt=f"Upload this file {image_url}") + assert (await page.locator("#uploadedImage").get_attribute("src")).__str__().startswith("data:image/jpeg") + + print("fallback") + await page.reload() + assert (await page.locator("#uploadedImage").get_attribute("src")).__str__() == "" + await page.upload_file("#imageUploadBroken", image_url, prompt="Upload this file") + assert (await page.locator("#uploadedImage").get_attribute("src")).__str__().startswith("data:image/jpeg") + print("all done") + + +@pytest.mark.asyncio +async def test_extract(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/click.html") + + result = await page.extract("give one sentence description of this page") + print(result) + assert "click" in str(result) + + result = await page.extract( + prompt="Describe this page", + schema={ + "type": "object", + "properties": { + "short": {"type": "string", "description": "one sentence description of this page"}, + "long": {"type": "string", "description": "two-three sentence description of this page"}, + }, + "required": ["short", "long"], + }, + ) + print(result) + assert "click" in str(result) + assert "short" in str(result) + assert "long" in str(result) + + +@pytest.mark.asyncio +async def test_prompt_locator(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/click.html") + + assert await page.locator("#counter").text_content() == "Button clicked 0 times" + await page.locator(prompt="Find the 'click me' button").click() + assert await page.locator("#counter").text_content() == "Button clicked 1 times" + + await page.locator(prompt="Find the 'click me' button").nth(0).click() + assert await page.locator("#counter").text_content() == "Button clicked 2 times" + + await page.locator("#bad-selector", prompt="Find the 'click me' button").click() + assert await page.locator("#counter").text_content() == "Button clicked 3 times" + + await page.locator("#bad-selector", prompt="Find the 'click me' button").nth(0).click() + assert await page.locator("#counter").text_content() == "Button clicked 4 times" + + await page.locator("#button").click() + assert await page.locator("#counter").text_content() == "Button clicked 5 times" + + +@pytest.mark.asyncio +async def test_prompt_locator_chaining(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/combobox.html") + + await page.locator("#cars").select_option("opel") + assert await page.locator("#cars").input_value() == "opel" + + await page.locator(prompt="Find the 'cars' combobox").select_option("saab") + assert await page.locator("#cars").input_value() == "saab" + + await page.locator("#bad-selector", prompt="Find the 'cars' combobox").select_option("audi") + assert await page.locator("#cars").input_value() == "audi" + + +@pytest.mark.asyncio +async def test_validate(web_server, skyvern_browser): + page = await skyvern_browser.get_working_page() + + await page.goto(f"{web_server}/click.html") + await page.click("#button") + + is_valid = await page.validate("if clicked time > 0") + assert is_valid + + is_valid = await page.validate("if clicked time > 1") + assert not is_valid + + # invalid prompt does not pass validation + is_valid = await page.validate("the input text is valid") + assert not is_valid + + +@pytest.mark.asyncio +async def test_prompt(skyvern_browser): + page = await skyvern_browser.get_working_page() + + r = await page.prompt("1111+1111") + print(r) + assert "2222" in str(r) + + r = await page.prompt( + "2+2", + schema={ + "type": "object", + "properties": { + "result_number": {"type": "int"}, + "confidence": {"type": "number", "minimum": 0, "maximum": 1}, + }, + }, + ) + print(r) + assert r["result_number"] == 4 diff --git a/tests/sdk/typescript_sdk/.gitignore b/tests/sdk/typescript_sdk/.gitignore new file mode 100644 index 00000000..61e3dc73 --- /dev/null +++ b/tests/sdk/typescript_sdk/.gitignore @@ -0,0 +1,3 @@ +node_modules +.env +*.png \ No newline at end of file diff --git a/tests/sdk/typescript_sdk/package-lock.json b/tests/sdk/typescript_sdk/package-lock.json new file mode 100644 index 00000000..c2ed6188 --- /dev/null +++ b/tests/sdk/typescript_sdk/package-lock.json @@ -0,0 +1,1279 @@ +{ + "name": "skyvern-ts-sdk-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "skyvern-ts-sdk-tests", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@skyvern/client": "file:../../../skyvern-ts/client", + "dotenv": "^17.2.3" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "http-server": "^14.1.1", + "tsx": "^4.7.0", + "typescript": "^5.0.0" + } + }, + "../../../skyvern-ts/client": { + "name": "@skyvern/client", + "version": "1.0.6", + "dependencies": { + "playwright": "^1.48.0" + }, + "devDependencies": { + "@biomejs/biome": "2.2.5", + "@types/node": "^18.19.70", + "msw": "2.11.2", + "ts-loader": "^9.5.1", + "typescript": "~5.7.2", + "vitest": "^3.2.4", + "webpack": "^5.97.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "../../skyvern-ts/client": { + "name": "@skyvern/client", + "version": "1.0.3", + "extraneous": true, + "dependencies": { + "playwright": "^1.48.0" + }, + "devDependencies": { + "@biomejs/biome": "2.2.5", + "@types/node": "^18.19.70", + "msw": "2.11.2", + "ts-loader": "^9.5.1", + "typescript": "~5.7.2", + "vitest": "^3.2.4", + "webpack": "^5.97.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@skyvern/client": { + "resolved": "../../../skyvern-ts/client", + "link": true + }, + "node_modules/@types/node": { + "version": "20.19.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.26.tgz", + "integrity": "sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/portfinder": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", + "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true, + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/tests/sdk/typescript_sdk/package.json b/tests/sdk/typescript_sdk/package.json new file mode 100644 index 00000000..e3fa57d8 --- /dev/null +++ b/tests/sdk/typescript_sdk/package.json @@ -0,0 +1,23 @@ +{ + "name": "skyvern-ts-sdk-tests", + "version": "1.0.0", + "description": "Playground for testing Skyvern TypeScript SDK", + "type": "module", + "scripts": { + "build": "tsc", + "serve": "http-server ../web -p 9010 --silent", + "test": "node run-test.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@skyvern/client": "file:../../../skyvern-ts/client", + "dotenv": "^17.2.3" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "http-server": "^14.1.1", + "tsx": "^4.7.0", + "typescript": "^5.0.0" + } +} diff --git a/tests/sdk/typescript_sdk/run-test.js b/tests/sdk/typescript_sdk/run-test.js new file mode 100755 index 00000000..8fea7d64 --- /dev/null +++ b/tests/sdk/typescript_sdk/run-test.js @@ -0,0 +1,91 @@ +#!/usr/bin/env node +import { spawn } from "child_process"; +import http from "http"; +import { fileURLToPath } from "url"; +import { dirname, resolve } from "path"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const WEB_DIR = resolve(__dirname, "../web"); +const PORT = 9010; + +let server; + +function startServer() { + return new Promise((resolve, reject) => { + const handler = (req, res) => { + import("http-server").then(({ default: httpServer }) => { + const serve = httpServer.createServer({ root: WEB_DIR }); + serve.emit("request", req, res); + }); + }; + + // Use http-server via spawn instead + server = spawn("npx", ["http-server", WEB_DIR, "-p", PORT, "--silent"], { + stdio: "inherit", + }); + + // Wait a bit for server to start + setTimeout(() => { + http + .get(`http://localhost:${PORT}`, () => { + console.log(`Web server started at http://localhost:${PORT}`); + resolve(); + }) + .on("error", () => { + // Retry after a short delay + setTimeout(() => { + http + .get(`http://localhost:${PORT}`, () => { + console.log(`Web server started at http://localhost:${PORT}`); + resolve(); + }) + .on("error", reject); + }, 500); + }); + }, 500); + }); +} + +function runTests(args) { + return new Promise((resolve, reject) => { + const testProcess = spawn("npx", ["tsx", ...args], { + stdio: "inherit", + }); + + testProcess.on("close", (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Tests failed with exit code ${code}`)); + } + }); + }); +} + +async function main() { + const testArgs = process.argv.slice(2); + + if (testArgs.length === 0) { + console.error("Usage: npm test [test-name]"); + console.error("Example: npm test test_simple_actions.ts testClicks"); + process.exit(1); + } + + try { + await startServer(); + await runTests(testArgs); + console.log("Tests completed successfully"); + } catch (error) { + console.error("Error:", error.message); + process.exit(1); + } finally { + if (server) { + server.kill(); + console.log("Web server stopped"); + } + } +} + +main(); diff --git a/tests/sdk/typescript_sdk/test_run_tasks.ts b/tests/sdk/typescript_sdk/test_run_tasks.ts new file mode 100644 index 00000000..45280703 --- /dev/null +++ b/tests/sdk/typescript_sdk/test_run_tasks.ts @@ -0,0 +1,109 @@ +import { Skyvern, SkyvernEnvironment } from "@skyvern/client"; +import "dotenv/config"; + +const WEB_SERVER = process.env.WEB_SERVER || "http://localhost:9010"; + +const skyvern = new Skyvern({ + apiKey: process.env.SKYVERN_API_KEY!, + environment: SkyvernEnvironment.Local, +}); + +async function testMlgameLogin() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/login.html`); + + const credentials = await skyvern.getCredentials(); + let credential = credentials.find((item) => item.name === "test_login"); + + if (!credential) { + console.log("Credentials not found. Creating new one."); + credential = await skyvern.createCredential({ + name: "test_login", + credential_type: "password", + credential: { + username: "testlogin", + password: "testpassword", + totp_type: "none", + }, + }); + } + + await page.agent.login("skyvern", { + credentialId: credential.credential_id, + }); + + await page.click("#accountBtn"); + await new Promise((resolve) => setTimeout(resolve, 1000)); + await page.act("Click on 'Click Me' button"); + console.assert(await page.locator("#clickCounter").textContent() == "Button clicked 1 times"); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await page.screenshot({ path: "screenshot.png", fullPage: true }); + + console.log("All tests passed"); +} + +async function testFinishesLogin() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto("https://www.saucedemo.com/"); + await page.fill("#user-name", "standard_user"); + await page.fill("#password", "secret_sauce"); + + await page.agent.runTask("Click on login button", { engine: "skyvern-1.0" }); + + console.assert((await page.getByRole("button", { name: "Add to cart" }).count()) > 0); + + console.log("All tests passed"); +} + +async function testDownloadFile() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/download_file.html`); + + const result = await page.agent.downloadFiles( + "Click the 'Download PDF Report' button to download the sample PDF file", + { downloadSuffix: "sample_report.pdf" } + ); + + console.log(result.downloaded_files); + console.assert(result.downloaded_files?.length === 1); + + await new Promise((resolve) => setTimeout(resolve, 2000)); + await page.screenshot({ path: "download_test.png", fullPage: true }); + + console.log("All tests passed"); +} + +const tests: Record Promise> = { + testMlgameLogin, + testFinishesLogin, + testDownloadFile, + all: async () => { + await testMlgameLogin(); + await testFinishesLogin(); + await testDownloadFile(); + }, +}; + +const testName = process.argv[2] || "all"; + +if (tests[testName]) { + tests[testName]() + .catch((error) => { + console.error("Test failed:", error); + process.exit(1); + }) + .finally(async () => { + await skyvern.close(); + }); +} else { + console.error(`Unknown test: ${testName}`); + console.error(`Available tests: ${Object.keys(tests).join(", ")}`); + process.exit(1); +} diff --git a/tests/sdk/typescript_sdk/test_simple_actions.ts b/tests/sdk/typescript_sdk/test_simple_actions.ts new file mode 100644 index 00000000..cbff4512 --- /dev/null +++ b/tests/sdk/typescript_sdk/test_simple_actions.ts @@ -0,0 +1,208 @@ +import { Skyvern, SkyvernEnvironment } from "@skyvern/client"; +import "dotenv/config"; + +const WEB_SERVER = process.env.WEB_SERVER || "http://localhost:9010"; + +const skyvern = new Skyvern({ + apiKey: process.env.SKYVERN_API_KEY!, + environment: SkyvernEnvironment.Local, +}); + +async function testClicks() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/click.html`); + + console.assert((await page.locator("#counter").textContent()) === "Button clicked 0 times"); + await page.click("#button"); + console.assert((await page.locator("#counter").textContent()) === "Button clicked 1 times"); + + await page.click({ prompt: "Click on the button" }); + console.assert((await page.locator("#counter").textContent()) === "Button clicked 2 times"); + + console.log("Fallback"); + await page.click("#broken-selector", { prompt: "Click on the button" }); + console.assert((await page.locator("#counter").textContent()) === "Button clicked 3 times"); + + await page.click("#button"); + console.assert((await page.locator("#counter").textContent()) === "Button clicked 4 times"); + + console.log("All tests passed"); +} + +async function testFill() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/input.html`); + + console.assert((await page.locator("#output").textContent()) === ""); + await page.fill("#nameInput", "Test1"); + await page.click("#submitBtn"); + console.assert((await page.locator("#output").textContent()) === "Hello, Test1!"); + + await page.fill({ prompt: "Type 'Test2' in the name input" }); + await page.click("#submitBtn"); + console.assert((await page.locator("#output").textContent()) === "Hello, Test2!"); + + await page.fill({ prompt: "Type the value in the name input", value: "Test3" }); + await page.click("#submitBtn"); + console.assert((await page.locator("#output").textContent()) === "Hello, Test3!"); + + await page.fill("#nameInputBroken", "TestFallback", { prompt: "Fill the name input" }) + await page.click("#submitBtn") + console.assert(await page.locator("#output").textContent() == "Hello, TestFallback!") + + console.log("All tests passed"); +} + +async function testSelectOption() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/combobox.html`); + + await page.selectOption("#cars", "audi"); + console.assert((await page.locator("#cars").inputValue()) === "audi"); + + await page.selectOption("#cars", "opel"); + console.assert((await page.locator("#cars").inputValue()) === "opel"); + + await page.selectOption("#cars", { label: "Saab" }); + console.assert((await page.locator("#cars").inputValue()) === "saab"); + + await page.selectOption({ prompt: "Select 'Audi' i the car combobox" }); + console.assert((await page.locator("#cars").inputValue()) === "audi"); + + // fallback + await page.selectOption("#cars-broken", "opel", { prompt: "Select 'Opel' i the car combobox" }) + console.assert(await page.locator("#cars").inputValue() == "opel") + + console.log("All tests passed"); +} + +async function testActCombobox() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/combobox.html`); + + await page.act("select 'Audi' from the combobox"); + + console.assert((await page.locator("#cars").inputValue()) === "audi"); + + console.log("All tests passed"); +} + +async function testActInputAndClick() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/input.html`); + + await page.act("type 'ActTest' into the input box"); + await page.act("click on the button"); + + console.assert((await page.locator("#output").textContent()) === "Hello, ActTest!"); + + console.log("All tests passed"); +} + +async function testExtract() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/click.html`); + + const r1 = await page.extract({ prompt: "give one sentence description of this page" }); + console.log(r1); + + const r2 = await page.extract({ + prompt: "Describe this page", + schema: { + type: "object", + properties: { + short: { type: "string", description: "one sentence description of this page" }, + long: { type: "string", description: "two-three sentence description of this page" }, + }, + required: ["short", "long"], + }, + }); + console.log(r2); + + console.log("All tests passed"); +} + +async function testValidate() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + await page.goto(`${WEB_SERVER}/click.html`); + await page.click("#button"); + + console.assert((await page.validate("if clicked time > 0")) === true); + console.assert((await page.validate("if clicked time > 1")) === false); + console.assert((await page.validate("the input text is valid")) === false); + + console.log("All tests passed"); +} + +async function testPrompt() { + const browser = await skyvern.connectToBrowserOverCdp("http://localhost:9222"); + const page = await browser.getWorkingPage(); + + const r1 = await page.prompt("1111+1111"); + console.log(r1); + console.assert(String(r1).includes("2222")); + + const r2 = await page.prompt("2+2", { + type: "object", + properties: { + result_number: { type: "number" }, + confidence: { type: "number", minimum: 0, maximum: 1 }, + }, + }); + console.log(r2); + console.assert((r2 as any).result_number === 4); + + console.log("All tests passed"); +} + +const tests: Record Promise> = { + testClicks, + testFill, + testSelectOption, + testActCombobox, + testActInputAndClick, + testExtract, + testValidate, + testPrompt, + all: async () => { + await testClicks(); + await testFill(); + await testSelectOption(); + await testActCombobox(); + await testActInputAndClick(); + await testExtract(); + await testValidate(); + await testPrompt(); + }, +}; + +const testName = process.argv[2] || "all"; + +if (tests[testName]) { + tests[testName]() + .catch((error) => { + console.error("Test failed:", error); + process.exit(1); + }) + .finally(async () => { + await skyvern.close(); + }); +} else { + console.error(`Unknown test: ${testName}`); + console.error(`Available tests: ${Object.keys(tests).join(", ")}`); + process.exit(1); +} diff --git a/tests/sdk/web/click.html b/tests/sdk/web/click.html new file mode 100644 index 00000000..d4888677 --- /dev/null +++ b/tests/sdk/web/click.html @@ -0,0 +1,65 @@ + + + + + + Click Counter + + + +

Click Counter

+ + + +
Button clicked 0 times
+ + + + diff --git a/tests/sdk/web/combobox.html b/tests/sdk/web/combobox.html new file mode 100644 index 00000000..26ad208b --- /dev/null +++ b/tests/sdk/web/combobox.html @@ -0,0 +1,98 @@ + + + + + + Dark Theme Select + + + + +

The select element

+ +

The select element is used to create a drop-down list.

+ +
+ + + +
+ +

Click the "Submit" button and the form-data will be sent to a page on the +server called "action_page.php".

+ + + diff --git a/tests/sdk/web/download_file.html b/tests/sdk/web/download_file.html new file mode 100644 index 00000000..efec8a65 --- /dev/null +++ b/tests/sdk/web/download_file.html @@ -0,0 +1,169 @@ + + + + + + File Download + + + +

File Download Test

+

Click the button below to download a sample PDF file

+ +
+ +
+ +
+ + + + diff --git a/tests/sdk/web/input.html b/tests/sdk/web/input.html new file mode 100644 index 00000000..921109b0 --- /dev/null +++ b/tests/sdk/web/input.html @@ -0,0 +1,110 @@ + + + + + + Text Input Demo + + + +

Enter Your Name

+ +
+ + +
+ +
+ + + + diff --git a/tests/sdk/web/login.html b/tests/sdk/web/login.html new file mode 100644 index 00000000..5a2c229a --- /dev/null +++ b/tests/sdk/web/login.html @@ -0,0 +1,308 @@ + + + + + + Login Demo + + + + +
+

Login

+
+
+ + +
+
+ + +
+ + +
+
+ + +
+

Dashboard

+
+ + +
+ + +
+

Account Information

+ +
+ +
Button clicked 0 times
+
+ + +
+ + + + diff --git a/tests/sdk/web/upload.html b/tests/sdk/web/upload.html new file mode 100644 index 00000000..3647f819 --- /dev/null +++ b/tests/sdk/web/upload.html @@ -0,0 +1,101 @@ + + + + + + Image Upload + + + +

Upload an Image

+ +
+ +
+ + + + + +