chore: first commit

This commit is contained in:
2026-01-19 08:54:54 +07:00
commit e1b9d48b3d
15 changed files with 1893 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

41
.github/workflows/monocart.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Monocart Report
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
pages: write
id-token: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps firefox
- name: Run tests
run: npx playwright test tests/api-command.spec.ts --project=firefox
continue-on-error: true
- name: Deploy report to GitHub Pages
if: always()
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: monocart-report

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/

277
data.json Normal file
View File

@@ -0,0 +1,277 @@
[
{
"commandType": "C",
"deviceName": "L",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "LV",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "L",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "MS",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "L",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "BT",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "L",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "WIC",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "BL",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "Open" },
"roomName": "LV",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "BL",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "Open" },
"roomName": "MS",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "BL",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "Open" },
"roomName": "JB",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "KN",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "A",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "KN",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "LV",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "A",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "LV",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "BR1",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "A",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "BR1",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "BR2",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "A",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "BR2",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "MS",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "A",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "MS",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "MR",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "AC",
"deviceType": "A",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "MR",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "FT1",
"deviceType": "A",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "LV",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "FT2",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "LV",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "PM25",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "LV",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "DL",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "KN",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "KC",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "KN",
"towerNumber": "T2",
"unitNumber": "01"
},
{
"commandType": "C",
"deviceName": "ERV",
"deviceType": "S",
"floorName": "L3",
"merchantName": "SAVY",
"payload": { "action": "On" },
"roomName": "LV",
"towerNumber": "T2",
"unitNumber": "01"
}
]

BIN
monocart-report.zip Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

861
package-lock.json generated Normal file
View File

@@ -0,0 +1,861 @@
{
"name": "playwright",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "playwright",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.57.0",
"@types/node": "^25.0.3",
"monocart-reporter": "^2.9.23"
}
},
"node_modules/@playwright/test": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.57.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@types/node": {
"version": "25.0.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/accepts/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/accepts/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-loose": {
"version": "8.5.2",
"resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-8.5.2.tgz",
"integrity": "sha512-PPvV6g8UGMGgjrMu+n/f9E/tCSkNQ2Y97eFvuVdJfG11+xdIeDcLyNdC8SHcrHbRqkfwLASdplyR6B6sKM1U4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.15.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/commander": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
"integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/console-grid": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/console-grid/-/console-grid-2.2.3.tgz",
"integrity": "sha512-+mecFacaFxGl+1G31IsCx41taUXuW2FxX+4xIE0TIPhgML+Jb9JFcBWGhhWerd1/vhScubdmHqTwOhB0KCUUAg==",
"dev": true,
"license": "MIT"
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookies": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
"integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
"dev": true,
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"keygrip": "~1.1.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/deep-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
"integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==",
"dev": true,
"license": "MIT"
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"dev": true,
"license": "MIT"
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"dev": true,
"license": "MIT"
},
"node_modules/eight-colors": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.1.tgz",
"integrity": "sha512-7nXPYDeKh6DgJDR/mpt2G7N/hCNSGwwoPVmoI3+4TEwOb07VFN1WMPG0DFf6nMEjrkgdj8Og7l7IaEEk3VE6Zg==",
"dev": true,
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"dev": true,
"license": "MIT"
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"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/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/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/http-assert": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
"integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"deep-equal": "~1.0.1",
"http-errors": "~1.8.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/http-assert/node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/http-assert/node_modules/http-errors": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/http-assert/node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC"
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/keygrip": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
"integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"dev": true,
"license": "MIT",
"dependencies": {
"tsscmp": "1.0.6"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/koa": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/koa/-/koa-3.1.1.tgz",
"integrity": "sha512-KDDuvpfqSK0ZKEO2gCPedNjl5wYpfj+HNiuVRlbhd1A88S3M0ySkdf2V/EJ4NWt5dwh5PXCdcenrKK2IQJAxsg==",
"dev": true,
"license": "MIT",
"dependencies": {
"accepts": "^1.3.8",
"content-disposition": "~0.5.4",
"content-type": "^1.0.5",
"cookies": "~0.9.1",
"delegates": "^1.0.0",
"destroy": "^1.2.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"fresh": "~0.5.2",
"http-assert": "^1.5.0",
"http-errors": "^2.0.0",
"koa-compose": "^4.1.0",
"mime-types": "^3.0.1",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/koa-compose": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz",
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==",
"dev": true,
"license": "MIT"
},
"node_modules/koa-static-resolver": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/koa-static-resolver/-/koa-static-resolver-1.0.6.tgz",
"integrity": "sha512-ZX5RshSzH8nFn05/vUNQzqw32nEigsPa67AVUr6ZuQxuGdnCcTLcdgr4C81+YbJjpgqKHfacMBd7NmJIbj7fXw==",
"dev": true,
"license": "MIT"
},
"node_modules/lz-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lz-utils/-/lz-utils-2.1.0.tgz",
"integrity": "sha512-CMkfimAypidTtWjNDxY8a1bc1mJdyEh04V2FfEQ5Zh8Nx4v7k850EYa+dOWGn9hKG5xOyHP5MkuduAZCTHRvJw==",
"dev": true,
"license": "MIT"
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/monocart-coverage-reports": {
"version": "2.12.9",
"resolved": "https://registry.npmjs.org/monocart-coverage-reports/-/monocart-coverage-reports-2.12.9.tgz",
"integrity": "sha512-vtFqbC3Egl4nVa1FSIrQvMPO6HZtb9lo+3IW7/crdvrLNW2IH8lUsxaK0TsKNmMO2mhFWwqQywLV2CZelqPgwA==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.15.0",
"acorn-loose": "^8.5.2",
"acorn-walk": "^8.3.4",
"commander": "^14.0.0",
"console-grid": "^2.2.3",
"eight-colors": "^1.3.1",
"foreground-child": "^3.3.1",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.2.0",
"lz-utils": "^2.1.0",
"monocart-locator": "^1.0.2"
},
"bin": {
"mcr": "lib/cli.js"
}
},
"node_modules/monocart-locator": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/monocart-locator/-/monocart-locator-1.0.2.tgz",
"integrity": "sha512-v8W5hJLcWMIxLCcSi/MHh+VeefI+ycFmGz23Froer9QzWjrbg4J3gFJBuI/T1VLNoYxF47bVPPxq8ZlNX4gVCw==",
"dev": true,
"license": "MIT"
},
"node_modules/monocart-reporter": {
"version": "2.9.23",
"resolved": "https://registry.npmjs.org/monocart-reporter/-/monocart-reporter-2.9.23.tgz",
"integrity": "sha512-FSz8908/2FIBJRMD9OdoVXcg7ndGmgiB3GmK2tojeMMrp5OSDt02ksv88GehlLRRzoL2jjf8fuSIHhi8u3QFxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"console-grid": "^2.2.3",
"eight-colors": "^1.3.1",
"koa": "^3.0.1",
"koa-static-resolver": "^1.0.6",
"lz-utils": "^2.1.0",
"monocart-coverage-reports": "^2.12.9",
"monocart-locator": "^1.0.2",
"nodemailer": "^7.0.6"
},
"bin": {
"monocart": "lib/cli.js"
}
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/nodemailer": {
"version": "7.0.12",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz",
"integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==",
"dev": true,
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/playwright": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.57.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"dev": true,
"license": "ISC"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/tsscmp": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.6.x"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"dev": true,
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
}
}
}

17
package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "playwright",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "npx playwright test",
"report": "npx monocart show-report monocart-report/index.html"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.57.0",
"@types/node": "^25.0.3",
"monocart-reporter": "^2.9.23"
}
}

85
playwright.config.ts Normal file
View File

@@ -0,0 +1,85 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['html'],
['monocart-reporter', {
name: "Savy API Test Report",
outputFile: './monocart-report/index.html'
}]
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'Tower-2_Floor-3_Unit-A',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://localhost:3000',
// reuseExistingServer: !process.env.CI,
// },
})

File diff suppressed because one or more lines are too long

217
tests/api-command.spec.ts Normal file
View File

@@ -0,0 +1,217 @@
import { test, expect } from '@playwright/test'
import topics from '../topic.json'
import crypto from 'crypto'
// Configuration
const API_URL = 'https://api-dev.vegacloud.id'
const API_COMMAND_PATH = '/device/v1/command'
const API_STATUS_PATH = '/device/v1/status'
const API_KEY = 'ak_sdbx:N8Xbvb4OYprJ-Mtw4QO6xw'
const SECRET_KEY = 'sk_sdbx:zyJteO7W3szL41fWWB8WZEmKpp8KyJzi-9o9OovbpT4'
// Convert "01" -> "A", "02" -> "B", etc.
function numToAlpha(numStr: string): string {
const num = parseInt(numStr, 10)
if (isNaN(num)) return numStr
return String.fromCharCode(64 + num)
}
// Helper function to sort keys deeply
function sortKeysDeep(obj: any): any {
if (Array.isArray(obj)) {
return obj.map(sortKeysDeep)
}
if (obj !== null && typeof obj === 'object') {
return Object.keys(obj)
.sort()
.reduce((acc: any, key) => {
acc[key] = sortKeysDeep(obj[key])
return acc
}, {})
}
return obj
}
// Generate signature for POST request
function generateSignatureForPost(body: any): { timestamp: number; nonce: string; signature: string } {
const timestamp = Math.floor(Date.now() / 1000)
const nonce = crypto.randomUUID()
const sortedBody = sortKeysDeep(body)
const jsonBody = JSON.stringify({
timestamp,
nonce,
body: JSON.stringify(sortedBody)
})
const signature = crypto
.createHmac('sha256', SECRET_KEY)
.update(jsonBody)
.digest('hex')
return { timestamp, nonce, signature }
}
// Parse query params from URL manually (matching Postman logic)
function parseQueryParamsManual(url: string): Record<string, string> {
const resultObject: Record<string, string> = {}
const queryStartIndex = url.indexOf('?')
if (queryStartIndex === -1 || queryStartIndex === url.length - 1) {
return resultObject
}
const queryString = url.substring(queryStartIndex + 1)
const pairs = queryString.split('&')
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i]
const parts = pair.split('=')
if (parts.length === 2) {
const key = decodeURIComponent(parts[0].replace(/\+/g, ' '))
const value = decodeURIComponent(parts[1].replace(/\+/g, ' '))
resultObject[key] = value
}
}
return resultObject
}
// Generate signature for GET request
function generateSignatureForGet(url: string): { timestamp: number; nonce: string; signature: string } {
const timestamp = Math.floor(Date.now() / 1000)
const nonce = crypto.randomUUID()
const params = parseQueryParamsManual(url)
const sortedParams = Object.keys(params)
.sort()
.reduce((acc: any, key) => {
acc[key] = params[key]
return acc
}, {})
const jsonBody = JSON.stringify({
timestamp,
nonce,
body: JSON.stringify(sortedParams)
})
const signature = crypto
.createHmac('sha256', SECRET_KEY)
.update(jsonBody)
.digest('hex')
return { timestamp, nonce, signature }
}
test.describe('Device Command & Status Tests', () => {
const getPayload = (deviceName: string) => {
if (deviceName === 'AC') return 'On'
if (deviceName === 'BL') return 'Open'
if (deviceName === 'DL') return 'Lock'
return null
}
test.describe('1. Command API Tests', () => {
for (const item of topics) {
const { towerNumber, floorName, unitNumber: originalUnit, deviceName, deviceRoom, deviceType } = item
const action = getPayload(deviceName)
if (action) {
const unitNumber = numToAlpha(originalUnit)
const payload = {
merchantName: 'SAVY',
floorName,
unitNumber,
deviceName,
roomName: deviceRoom,
deviceType,
commandType: 'C',
towerNumber,
payload: { action }
}
test(`POST Command: ${deviceName} (${deviceType}) - ${floorName}_${unitNumber} - ${deviceRoom}`, async ({ request }) => {
const { timestamp, nonce, signature } = generateSignatureForPost(payload)
console.log(`[Command] Request: ${JSON.stringify(payload, null, 2)}`)
const response = await request.post(`${API_URL}${API_COMMAND_PATH}`, {
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
'X-Timestamp': timestamp.toString(),
'X-Nonce': nonce,
'X-Signature': signature
},
data: payload
})
const responseBody = await response.json()
console.log(`[Command] Response Status: ${response.status()}`)
console.log(`[Command] Response Body: ${JSON.stringify(responseBody, null, 2)}`)
console.log('---')
expect(response.status()).toBe(200)
})
}
}
})
test.describe('2. Status Check API Tests', () => {
for (const item of topics) {
const { towerNumber, floorName, unitNumber: originalUnit, deviceName, deviceRoom, deviceType } = item
const action = getPayload(deviceName)
if (action) {
const unitNumber = numToAlpha(originalUnit)
test(`GET Status: ${deviceName} (${deviceType}) - ${floorName}_${unitNumber} - ${deviceRoom}`, async ({ request }) => {
const params = {
merchantName: 'SAVY',
floorName,
unitNumber,
deviceName,
roomName: deviceRoom,
deviceType,
commandType: 'S',
towerNumber
}
const queryString = new URLSearchParams(params).toString()
const url = `${API_URL}${API_STATUS_PATH}?${queryString}`
const { timestamp, nonce, signature } = generateSignatureForGet(url)
console.log(`[Status Check] Request URL: ${url}`)
const response = await request.get(url, {
headers: {
'X-API-Key': API_KEY,
'X-Timestamp': timestamp.toString(),
'X-Nonce': nonce,
'X-Signature': signature
}
})
const responseBody = await response.json()
console.log(`[Status Check] Response Status: ${response.status()}`)
console.log(`[Status Check] Response Body: ${JSON.stringify(responseBody, null, 2)}`)
console.log('---')
expect(response.status()).toBe(200)
expect(responseBody.data).not.toBeNull()
// Strict Validation
expect(responseBody.data.floorName).toBe(floorName)
expect(responseBody.data.unitNumber).toBe(unitNumber)
expect(responseBody.data.deviceName).toBe(deviceName)
expect(responseBody.data.roomName).toBe(deviceRoom)
expect(responseBody.data.deviceType).toBe(deviceType)
expect(responseBody.data.code).toBe('S')
expect(responseBody.data.towerNumber).toBe(towerNumber)
expect(responseBody.data).toHaveProperty('payload')
})
}
}
})
})

77
tests/command-api.spec.ts Normal file
View File

@@ -0,0 +1,77 @@
import { test, expect } from '@playwright/test'
import topics from '../topic.json'
test.describe('Savy API Automation', () => {
// Helper to determine payload
const getPayload = (deviceName: string) => {
if (deviceName === 'AC') return 'AC=On'
if (deviceName === 'BL') return 'Bind=Open'
if (deviceName === 'DL') return 'Doorlock=Lock'
return null
}
test.describe('1. Command API Tests', () => {
for (const item of topics) {
const { towerNumber, floorName, unitNumber, deviceName, deviceRoom, deviceType } = item
const payload = getPayload(deviceName)
if (payload) {
const topic = `SAVY/${floorName}_${unitNumber}-${deviceName}-${deviceRoom}-${deviceType}-C-${towerNumber}`
test(`Send command for ${deviceName} - ${topic}`, async ({ request }) => {
const requestData = { topic, payload }
console.log(`[Command] Request: ${JSON.stringify(requestData, null, 2)}`)
const response = await request.post('http://savy.vegacloud.id/command', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic e3thdXRodXNlckhUVFB9fTp7e2F1dGhwd2RIVFRQfX0=',
},
data: requestData
})
const responseBody = await response.json()
console.log(`[Command] Response Status: ${response.status()}`)
console.log(`[Command] Response Body: ${JSON.stringify(responseBody, null, 2)}`)
console.log('---')
expect(response.status()).toBe(200)
})
}
}
})
test.describe('2. State Reply API Tests', () => {
for (const item of topics) {
const { towerNumber, floorName, unitNumber, deviceName, deviceRoom, deviceType } = item
const payload = getPayload(deviceName)
if (payload) {
const topic = `SAVY/${floorName}_${unitNumber}-${deviceName}-${deviceRoom}-${deviceType}-S-${towerNumber}`
test(`Check state for ${deviceName} - ${topic}`, async ({ request }) => {
const requestData = { topic }
console.log(`[State Reply] Request: ${JSON.stringify(requestData, null, 2)}`)
const response = await request.post('http://savy.vegacloud.id/state-reply', {
headers: {
'Content-Type': 'application/json'
},
data: requestData
})
const responseBody = await response.json()
console.log(`[State Reply] Response Status: ${response.status()}`)
console.log(`[State Reply] Response Body: ${JSON.stringify(responseBody, null, 2)}`)
console.log('---')
expect(response.status()).toBe(200)
expect(responseBody.data).not.toBeNull()
})
}
}
})
})

18
tests/example.spec.ts Normal file
View File

@@ -0,0 +1,18 @@
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Playwright/);
});
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Click the get started link.
await page.getByRole('link', { name: 'Get started' }).click();
// Expects page to have a heading with the name of Installation.
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

154
topic.json Normal file
View File

@@ -0,0 +1,154 @@
[
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "L",
"deviceRoom": "LV",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "L",
"deviceRoom": "MS",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "L",
"deviceRoom": "BT",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "L",
"deviceRoom": "WIC",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "BL",
"deviceRoom": "LV",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "BL",
"deviceRoom": "MS",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "AC",
"deviceRoom": "LV",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "AC",
"deviceRoom": "LV",
"deviceType": "A"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "AC",
"deviceRoom": "BR1",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "AC",
"deviceRoom": "BR1",
"deviceType": "A"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "AC",
"deviceRoom": "BR2",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "AC",
"deviceRoom": "BR2",
"deviceType": "A"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "AC",
"deviceRoom": "MS",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "AC",
"deviceRoom": "MS",
"deviceType": "A"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "FT1",
"deviceRoom": "LV",
"deviceType": "A"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "FT2",
"deviceRoom": "LV",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "PM25",
"deviceRoom": "LV",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "DL",
"deviceRoom": "KN",
"deviceType": "S"
},
{
"towerNumber": "T2",
"floorName": "L29",
"unitNumber": "05",
"deviceName": "KC",
"deviceRoom": "KN",
"deviceType": "S"
}
]