From a78c7554162f0da2ca726ec6deca56dc6e8a810d Mon Sep 17 00:00:00 2001 From: Taylor Price Date: Wed, 19 Nov 2025 16:11:28 -0700 Subject: [PATCH 1/4] wip: run matrix on dispatch from PR Signed-off-by: Taylor Price --- .github/workflows/automation.yml | 2 - .github/workflows/pr-trigger.yml | 117 +++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/pr-trigger.yml diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index f44a6cf..f6e09c8 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -2,8 +2,6 @@ name: Run WDIO Tests with OBOT Docker on: workflow_dispatch: - repository_dispatch: - types: [pr-created] jobs: wdio-tests: diff --git a/.github/workflows/pr-trigger.yml b/.github/workflows/pr-trigger.yml new file mode 100644 index 0000000..9c2f464 --- /dev/null +++ b/.github/workflows/pr-trigger.yml @@ -0,0 +1,117 @@ +name: Run WDIO Tests with OBOT Docker + +on: + repository_dispatch: + types: [pr-created] + +jobs: + prepare-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + pr_number: ${{ steps.set-matrix.outputs.pr_number }} + steps: + - name: Set matrix data + id: set-matrix + run: | + if [ "${{ github.event_name }}" = "repository_dispatch" ]; then + echo "matrix=${{ toJson(github.event.client_payload.changed_files) }}" >> $GITHUB_OUTPUT + echo "pr_number=${{ github.event.client_payload.pr_number }}" >> $GITHUB_OUTPUT + else + # Default empty matrix for workflow_dispatch + echo 'matrix=[{"file":"default","containerizedConfig":null,"env":null}]' >> $GITHUB_OUTPUT + echo "pr_number=" >> $GITHUB_OUTPUT + fi + + wdio-tests: + needs: prepare-matrix + runs-on: ubuntu-latest + if: needs.prepare-matrix.outputs.matrix != '[]' + strategy: + fail-fast: false + matrix: + server: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }} + + permissions: + id-token: write + actions: read + checks: read + + steps: + - name: Checkout Repository + uses: actions/checkout@v5 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: Cache NPM Dependencies + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run OBOT container + run: | + docker run -d --name obot \ + -p 8080:8080 \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }} \ + -e OBOT_SERVER_DISABLE_UPDATE_CHECK=true \ + ghcr.io/obot-platform/obot:latest + + - name: Wait for OBOT to be ready + run: | + echo "Waiting for OBOT container..." + for i in {1..12}; do + if curl -sf http://localhost:8080/api/me | grep -q '"username":"nobody"'; then + echo "OBOT API is ready." + break + fi + echo "Not ready yet... retry #$i" + sleep 5 + done + + - name: Create catalog entry + run: | + curl localhost:8080/api/mcp-catalogs/default/entries -X POST -H "Content-Type: application/json" \ + -d '{ + "name": "test-${{ replace(matrix.server.file, '.yaml', '') }}", + "description": "testing ${{ replace(matrix.server.file, '.yaml', '') }} mcp", + "icon": "https://avatars.githubusercontent.com/u/9919?v=4", + "repoURL": "https://github.com/testing/testing", + "runtime": "containerized", + "containerizedConfig": { + "image": "${{ matrix.server.containerizedConfig.image }}", + "port": ${{ matrix.server.containerizedConfig.port }}, + "path": "${{ matrix.server.containerizedConfig.path }}", + "args": ${{ toJson(matrix.server.containerizedConfig.args) } + }, + "metadata": { + "categories": "testing" + } + }' + + - name: Install dependencies + run: npm ci + + - name: Run WDIO Tests + env: + WP_URL: ${{ secrets.WP_URL }} + WP_USERNAME: ${{ secrets.WP_USERNAME }} + WP_PASSWORD: ${{ secrets.WP_PASSWORD }} + OBOT_URL: ${{ secrets.OBOT_URL }} + GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY}} + run: | + npx wdio run wdio.conf.ts --cucumberOpts.tagExpression='@${{ replace(matrix.server.file, '.yaml', '') }}' From 7d1bfda04ea0a7f0ccd61efcf0125d54b815dd5d Mon Sep 17 00:00:00 2001 From: Taylor Price Date: Wed, 19 Nov 2025 16:13:46 -0700 Subject: [PATCH 2/4] chore: trigger specific scenarios per mcp server Signed-off-by: Taylor Price --- README.md | 4 +- src/core/selectors.ts | 507 ++++++++++++++++++--------------- src/features/mcpServer.feature | 10 +- src/steps/mcpServer.step.ts | 145 ++++++---- 4 files changed, 372 insertions(+), 294 deletions(-) diff --git a/README.md b/README.md index ce4249b..fd04489 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ Once you've installed the dependencies and set up your environment, you can run 2. **Run a specific test**: If you want to run a specific scenario or feature, use: ```bash - npm run wdio:byScenario --spec src/features/obot.feature:10 + npx wdio run wdio.conf.ts --cucumberOpts.tagExpression='@gitlab' ``` - Replace `10` with the line number of the scenario you want to execute. + Replace `@gitlab` with the tag corresponding to the scenario you want to execute. 3. **Run in headless mode** (useful for CI/CD): You can run the tests in headless mode (without opening the browser window) by configuring the `wdio.conf.ts` file to enable headless execution, and then run the tests with: diff --git a/src/core/selectors.ts b/src/core/selectors.ts index f51a671..a0e9465 100644 --- a/src/core/selectors.ts +++ b/src/core/selectors.ts @@ -1,231 +1,280 @@ const Selectors = { - loginButton: '//button[text()="Login"]', - obotPopUp: '//dialog[contains(@class, "fixed top-1/2 left-1/2")]', - continueWithOkta: '//span[text()="Continue with Okta"]', - continueWithGoogle: '//span[text()="Continue with Google"]', - clickonAccount: '//div[text()="Use another account"]', - emailId: '//input[@type="email"]', - usernameField: '//input[@type="text"]', - passwordField: '//input[@type="password"]', - submitButton: '//input[@type="submit"]', - createnewobotButton: "//button[contains(@class, 'button') and contains(., 'Create New Agent')]", - editpencilIcon: '//*[contains(@class,"pencil h-5")]/ancestor::button[contains(@class,"group text")]', - messageContent: '//div[@class="flex w-full items-center gap-4 p-2"]//textarea[@placeholder="Your message..."]', - updateUsername: '//input[@class="bg-surface grow rounded-lg p-2"]', - exitEditor: '//button[@class="group relative mr-1 flex items-center rounded-full border p-2 text-xs transition-[background-color] duration-200 border-blue bg-blue text-white md:px-4"]', - fileIcon:'//label[text()="Your messages"]/following-sibling::div//button', - obotUpdatedname: '//h4[@class="mb-1!"]', - deleteButton: '//button[@class="button-destructive"]', - deleteTool:'(//button[@class="icon-button-small"])[1]', - fileuploadPopup:"//div[contains(@class, 'default-dialog') and @style='left: 166px; top: 12.833px;']", - fileuploadButton:"(//label[contains(@class, 'button') and contains(@class, 'flex') and contains(., 'Upload')])[2]", - filenameVerify:'//span[text()="AI_test.txt"]', - fileUpload:'//label[contains(normalize-space(), "Upload")]//input[@type="file" and @accept=".pdf, .txt, .doc, .docx, .ppt, .pptx, .md, .rtf, .html, .odt, .ipynb, .json,.csv, .png, .jpg, .jpeg, .webp"]', - confirmDeleteObotButton: '//h3[text()="Delete the current Obot?"]/following-sibling::button[contains(text(), "Yes") and contains(@class, "bg-red-600")]', - clickChatbot:'//button[@class="icon-button group relative flex items-center gap-2 p-0 shadow-md"]', - clickOnDiv:'//div[@id="click-catch"]', - clickshowOutput:'//button[text()="Show Output"]', - chatOutputElement:'//p[text()="Output"]/following-sibling::pre[contains(@class,"text")]', - task: { - expandTaskMenu: '//div[@class="flex items-center justify-between"]//button[@class="icon-button"][1]', - createNewTaskButton: '//button[contains(., "New Task")]', - strongTask: '//strong[text()="TASK"]', - newTaskTitle: '//input[@class="ghost-input text-2xl font-semibold"]', - deleteTask: '//strong[text()="TASK"]/../..//button[@class="button-destructive p-4"]', - confirmDeleteTask: '//h3[contains(text(),"delete this task")]/following-sibling::button[contains(text(), "Yes") and contains(@class, "bg-red-600")]', - stepsInput: '//textarea[@class="ghost-input border-surface2 ml-1 grow resize-none scrollbar-none"]', - runButton: "(//button[contains(@class, 'bg-blue') and contains(@class, 'text-white')])", - advancedOptionBtn:'//button[text()="Show Advanced Options..."]', - argumentBtn: '(//button[contains(text(),"Argument")])', - inputArgument: '//input[@placeholder="Enter Name"]', - argumentPopup: '//h4[text()="Arguments"]/following-sibling::div//input[@id="param-tax calculator"]', - argumentValue:'//input[@id="param-Enter your income"]', - submitArgs: "(//button[text()='Run' and contains(@class, 'button-primary')])[2]", - inputResult: "(//div[contains(@class, 'relative') and contains(@class, 'my-3')])[3]", - inputTextHeader: '//p[contains(text(),"Here is the tax on your income")]', - inputText:'//span[@class="text-sm font-semibold" and contains(text(),"Automation")]', - searchBot: '//textarea[@id="chat"]', - submitButton:'//button[@type="submit" and contains(@class, "button-colors") and contains(@class, "text-blue")]', - triggerTypeBtn:'(//button[normalize-space(text())="on demand"])[1]', - selectTriggerType: (option: string) => `//button[normalize-space(text())='${option}']`, - webHookURL:'//div[contains(@class, "bg-surface2") and contains(., "https://")]', - runTask:'//div[contains(@class, "flex") and contains(@class, "truncate")]', - taskResult:'//ul[@class="ml-4 flex flex-col text-xs"]//li[1]//div', - tableResult:'//span[text()="Tables "]/../following-sibling::div/div/div/ul/li/button', - tableRefresh:'//button[text()=" Refresh"]', - TaskMenu:'//button[.//span[contains(text(), "Tasks")]]', - sideBar:'(//button[@class="icon-button"])[1]', - timeTypeBtn: (option: string) => `(//button[normalize-space(text())='${option}'])[1]`, - clickTask:'//span[text()="Tasks "]', - createNewTask:'//button[text()=" New Task"]', - loopStep:'//button[@data-testid="step-loop-btn"]', - btnrun:'//button[text()="Run "]', - }, - table:{ - id:'//tbody//tr//td[1]', - name:'//tbody//tr//td[2]', - email:'//tbody//tr//td[3]', - tableTask:'(//div/child::textarea)[2]', - submitInput:'(//button[@type="submit" and contains(@class,"button-colors")])[2]', - }, - toolIcon: '//button[@class="button-icon-primary"]', - addTool: '//button[contains(text(),"Tools")]', - searchTools: '//input[contains(@placeholder,"Search tools")]', - selectToolQuery: `(//span[normalize-space(text())="{{toolName}}"])[2]`, - applyTool: '//button[text()="Apply"]', - uploadButton: '//label[text()=" Upload "]', - expandknowledgeFileicon: '//span[text()="Knowledge "]', - expandFileicon:'//span[text()="Files"]/parent::button[@class="flex items-center gap-2 px-5 py-2"]', - scrollableDiv: '//div[@class="default-scrollbar-thin flex grow flex-col"]', - verifytheKnowlegefileName: '//span[@class="ms-3 truncate"]', - verifythefileName:'//span[@class="truncate"]', - knowledgeFileUpload: '//label[contains(normalize-space(), "Upload")]//input[@type="file" and @accept=".pdf, .txt, .doc, .docx"]', - showDetails: '//button[text()="Show Details"]', - chatbotTextArea: '//div/textarea[@placeholder="Your message..."]', - submitChat: '//button[@type="submit" and contains(@class,"button-colors")]', - showAdvanceOptions: '//span[text()="Show Advanced Options..."]', - advancedOptionTabs: { - interfaceTab: '//span[text()="Interface"]', - tasksTab: '//span[text()="Tasks"]', - }, - chatIntroductionTextArea: '//div/textarea[@placeholder="Introduction"]', - starterMsgButton: '//span[text()="Starter Message"]/ancestor::button[contains(@class,"button")]', - starterMsgInput: '//textarea[@id="project-instructions"]', - chatIntroductionInObot: '//div[contains(@class,"message-content")]', - chatStarterMsgInObot: '//button/span[@class="line-clamp-3"]', - agentCatalog:'//a[text()="Agent Catalog"]', - createNewAgent:'//button[contains(@class, "bg-surface1")]', - agentEditHover:'//div[@class="flex flex-col items-center justify-center text-center"]', - agentEditPencilIcon:'(//button[contains(@class, "rounded-full")])[1]', - updateAgentName:'//input[contains(@class, "ghost-input")]', - confirmDelete:'(//button[normalize-space(text())="Yes, I\'m sure"])[3]', - tableInput:'//pre[contains(@class, "whitespace-pre-wrap")]//span[contains(@class, "text-gray-500")]', - showDetails2: '(//button[text()="Show Details"])[2]', - showDetails3: '(//button[text()="Show Details"])[3]', + loginButton: '//button[text()="Login"]', + obotPopUp: '//dialog[contains(@class, "fixed top-1/2 left-1/2")]', + continueWithOkta: '//span[text()="Continue with Okta"]', + continueWithGoogle: '//span[text()="Continue with Google"]', + clickonAccount: '//div[text()="Use another account"]', + emailId: '//input[@type="email"]', + usernameField: '//input[@type="text"]', + passwordField: '//input[@type="password"]', + submitButton: '//input[@type="submit"]', + createnewobotButton: + "//button[contains(@class, 'button') and contains(., 'Create New Agent')]", + editpencilIcon: + '//*[contains(@class,"pencil h-5")]/ancestor::button[contains(@class,"group text")]', + messageContent: + '//div[@class="flex w-full items-center gap-4 p-2"]//textarea[@placeholder="Your message..."]', + updateUsername: '//input[@class="bg-surface grow rounded-lg p-2"]', + exitEditor: + '//button[@class="group relative mr-1 flex items-center rounded-full border p-2 text-xs transition-[background-color] duration-200 border-blue bg-blue text-white md:px-4"]', + fileIcon: '//label[text()="Your messages"]/following-sibling::div//button', + obotUpdatedname: '//h4[@class="mb-1!"]', + deleteButton: '//button[@class="button-destructive"]', + deleteTool: '(//button[@class="icon-button-small"])[1]', + fileuploadPopup: + "//div[contains(@class, 'default-dialog') and @style='left: 166px; top: 12.833px;']", + fileuploadButton: + "(//label[contains(@class, 'button') and contains(@class, 'flex') and contains(., 'Upload')])[2]", + filenameVerify: '//span[text()="AI_test.txt"]', + fileUpload: + '//label[contains(normalize-space(), "Upload")]//input[@type="file" and @accept=".pdf, .txt, .doc, .docx, .ppt, .pptx, .md, .rtf, .html, .odt, .ipynb, .json,.csv, .png, .jpg, .jpeg, .webp"]', + confirmDeleteObotButton: + '//h3[text()="Delete the current Obot?"]/following-sibling::button[contains(text(), "Yes") and contains(@class, "bg-red-600")]', + clickChatbot: + '//button[@class="icon-button group relative flex items-center gap-2 p-0 shadow-md"]', + clickOnDiv: '//div[@id="click-catch"]', + clickshowOutput: '//button[text()="Show Output"]', + chatOutputElement: + '//p[text()="Output"]/following-sibling::pre[contains(@class,"text")]', + task: { + expandTaskMenu: + '//div[@class="flex items-center justify-between"]//button[@class="icon-button"][1]', + createNewTaskButton: '//button[contains(., "New Task")]', + strongTask: '//strong[text()="TASK"]', + newTaskTitle: '//input[@class="ghost-input text-2xl font-semibold"]', + deleteTask: + '//strong[text()="TASK"]/../..//button[@class="button-destructive p-4"]', + confirmDeleteTask: + '//h3[contains(text(),"delete this task")]/following-sibling::button[contains(text(), "Yes") and contains(@class, "bg-red-600")]', + stepsInput: + '//textarea[@class="ghost-input border-surface2 ml-1 grow resize-none scrollbar-none"]', + runButton: + "(//button[contains(@class, 'bg-blue') and contains(@class, 'text-white')])", + advancedOptionBtn: '//button[text()="Show Advanced Options..."]', + argumentBtn: '(//button[contains(text(),"Argument")])', + inputArgument: '//input[@placeholder="Enter Name"]', + argumentPopup: + '//h4[text()="Arguments"]/following-sibling::div//input[@id="param-tax calculator"]', + argumentValue: '//input[@id="param-Enter your income"]', + submitArgs: + "(//button[text()='Run' and contains(@class, 'button-primary')])[2]", + inputResult: + "(//div[contains(@class, 'relative') and contains(@class, 'my-3')])[3]", + inputTextHeader: '//p[contains(text(),"Here is the tax on your income")]', + inputText: + '//span[@class="text-sm font-semibold" and contains(text(),"Automation")]', + searchBot: '//textarea[@id="chat"]', + submitButton: + '//button[@type="submit" and contains(@class, "button-colors") and contains(@class, "text-blue")]', + triggerTypeBtn: '(//button[normalize-space(text())="on demand"])[1]', + selectTriggerType: (option: string) => + `//button[normalize-space(text())='${option}']`, + webHookURL: + '//div[contains(@class, "bg-surface2") and contains(., "https://")]', + runTask: '//div[contains(@class, "flex") and contains(@class, "truncate")]', + taskResult: '//ul[@class="ml-4 flex flex-col text-xs"]//li[1]//div', + tableResult: + '//span[text()="Tables "]/../following-sibling::div/div/div/ul/li/button', + tableRefresh: '//button[text()=" Refresh"]', + TaskMenu: '//button[.//span[contains(text(), "Tasks")]]', + sideBar: '(//button[@class="icon-button"])[1]', + timeTypeBtn: (option: string) => + `(//button[normalize-space(text())='${option}'])[1]`, + clickTask: '//span[text()="Tasks "]', + createNewTask: '//button[text()=" New Task"]', + loopStep: '//button[@data-testid="step-loop-btn"]', + btnrun: '//button[text()="Run "]', + }, + table: { + id: "//tbody//tr//td[1]", + name: "//tbody//tr//td[2]", + email: "//tbody//tr//td[3]", + tableTask: "(//div/child::textarea)[2]", + submitInput: + '(//button[@type="submit" and contains(@class,"button-colors")])[2]', + }, + toolIcon: '//button[@class="button-icon-primary"]', + addTool: '//button[contains(text(),"Tools")]', + searchTools: '//input[contains(@placeholder,"Search tools")]', + selectToolQuery: `(//span[normalize-space(text())="{{toolName}}"])[2]`, + applyTool: '//button[text()="Apply"]', + uploadButton: '//label[text()=" Upload "]', + expandknowledgeFileicon: '//span[text()="Knowledge "]', + expandFileicon: + '//span[text()="Files"]/parent::button[@class="flex items-center gap-2 px-5 py-2"]', + scrollableDiv: '//div[@class="default-scrollbar-thin flex grow flex-col"]', + verifytheKnowlegefileName: '//span[@class="ms-3 truncate"]', + verifythefileName: '//span[@class="truncate"]', + knowledgeFileUpload: + '//label[contains(normalize-space(), "Upload")]//input[@type="file" and @accept=".pdf, .txt, .doc, .docx"]', + showDetails: '//button[text()="Show Details"]', + chatbotTextArea: '//div/textarea[@placeholder="Your message..."]', + submitChat: '//button[@type="submit" and contains(@class,"button-colors")]', + showAdvanceOptions: '//span[text()="Show Advanced Options..."]', + advancedOptionTabs: { + interfaceTab: '//span[text()="Interface"]', + tasksTab: '//span[text()="Tasks"]', + }, + chatIntroductionTextArea: '//div/textarea[@placeholder="Introduction"]', + starterMsgButton: + '//span[text()="Starter Message"]/ancestor::button[contains(@class,"button")]', + starterMsgInput: '//textarea[@id="project-instructions"]', + chatIntroductionInObot: '//div[contains(@class,"message-content")]', + chatStarterMsgInObot: '//button/span[@class="line-clamp-3"]', + agentCatalog: '//a[text()="Agent Catalog"]', + createNewAgent: '//button[contains(@class, "bg-surface1")]', + agentEditHover: + '//div[@class="flex flex-col items-center justify-center text-center"]', + agentEditPencilIcon: '(//button[contains(@class, "rounded-full")])[1]', + updateAgentName: '//input[contains(@class, "ghost-input")]', + confirmDelete: '(//button[normalize-space(text())="Yes, I\'m sure"])[3]', + tableInput: + '//pre[contains(@class, "whitespace-pre-wrap")]//span[contains(@class, "text-gray-500")]', + showDetails2: '(//button[text()="Show Details"])[2]', + showDetails3: '(//button[text()="Show Details"])[3]', - admin:{ - oktaLogin:'//button[normalize-space(.//div) = "Sign in with Okta"]', - crateNewAgent:'//button[normalize-space(.//div) = "New Agent"]', - agentName:'(//div/child::input)[1]', - agentDes:'(//div/child::input)[2]', - tryItOutBtn:'//div/child::a', - editAgent:'(//a[contains(@href, "/admin/agents/")])[1]', - deleteAgent:'(//a[contains(@href, "/admin/agents/")]/following-sibling::button)[1]', - confirmDelete:'//div[text()="Confirm"]', - agenetDeletedMessagge:'//div[@data-content]//div[contains(text(), "Agent deleted")]', - memoryDropdown:'//p[text()="Memory"]/ancestor::div[contains(@class, "flex")]/following-sibling::div//button', - setAlwaysOn:'//div[contains(@role, "option")]/span[contains(text(),"Always On")]', - addTool:'//div[text()=" Add Tools"]', - addKnowledge:'//div[text()="Add Knowledge"]', - localFiles:'//div[text()="Local Files"]', - searchTool:'//input[@placeholder="Search tools..."]', - googleSearch:'//span[text()="Google Search "]', - weather:'//span[text()="Weather "]', - viewAllMemories:'//button[text()="View All Memories"]', - advanced:'//h4[text()="Advanced"]', - labelClick: `(//label[normalize-space(text())="{{labelClick}}"])`, - userleftsideMenu:'//span[text()="Users"]', - userFilter:'//span[text()="User"]', - searchText:'//input[@placeholder="Filter..."]', - clickonthreedotBtn:'//div[@class="flex justify-end"]//button[@type="button"]', - updateroleBtn:'//div[text()="Update Role"]', - clickonroleDrp:'//button[@aria-controls="radix-:rceg:"]', - selectRole:'//span[text()="{{role}}"]', - updateBtn:'//div[text()="Update"]', - deleteroleBtn:'//div[text()="Delete User"]', - deleteBtn:'//div[text()="Delete"]', - chatthreadMenu:'//a//span[text()="Chat Threads"]', - }, - agentDes:'//p[@class="text-gray w-sm font-light md:w-md"]', - authLink:'//a[contains(@class, "bg-blue") and contains(@class, "rounded-3xl") and contains(@class, "text-white")]', - scheduleDay:'(//button[normalize-space(text())="daily"])[1]', - scheduleTime:'(//button[normalize-space(text())="on the hour"])[1]', - homeButton:'//a[@id="navbar-home-link"]', - continueBtn:'//button[contains(text(), "I dunno")]/following-sibling::button', - lunchAgent:'//button[normalize-space(text())="Launch Agent"]', - obotEditArea:'//button[@id="edit-basic-details-button"]', - editAreaPencil: '(//button[@id="edit-basic-details-button"]/child::div)[2]', - editAgentName:'//input[@id="project-name"]', - obotNav:'(//div/child::span/following-sibling::button)[1]', - closeEditArea:'//button[@class="icon-button absolute top-2 right-2"]', - addKnowledge:'//p[text()="Knowledge"]/following-sibling::div', - clicksNewThread:'//a[.//img[@alt="Obot icon"]]/following-sibling::button', - memoryValidate:'//tr//td[2]', - refreashMemory:'//span[text()="Most recent memories"]/../div', - memoryIcon:'//button[@data-memories-btn and contains(@class, "icon-button")]', - closeMemory:'(//button[contains(@class, "absolute top-0 right-0 p-3")])[1]', - deleteAllMemory:'(//div//button[contains(text()," Delete All")])[2]', - deleteMemoryValidate:'//p[text()="No memories stored"]', - startFromScratch:'//button[text()=" Create From Scratch"]', - configuration:'//span[text()="Configuration "]', - editObotName:'//span[text()="Name & Description "]', - mcpServer:'//span[text()="MCP Servers "]', - addMCPServer:'//button[text()=" Add MCP Server"]', - searchMCPTool: '//input[@placeholder="Search MCP Servers..."]', - selectedTool: `(//h4[normalize-space(text())="{{mcpTool}}"])`, - mcpStep: `(//button[(text())="{{mcpStep}}"])`, - selectServer:'(//button[text()="Select Server "])', - addServer:'//button[text()="Add server"]', - validateMCPTool: (option: string) => `//p[normalize-space(text())="${option}"]`, - knowledge:'//span[text()="Knowledge "]', - selectFromSidebar:`(//span[(text())="{{select}}"])`, - enableChatBot:'//h5[text()="Enable"]/../label', - copyTemplates:'//div/a[@class="overflow-hidden text-sm text-ellipsis hover:underline"]', - copyChatBot:'//div/a[@class="overflow-hidden text-ellipsis hover:underline"]', - validateThread:'//button[text()="New Thread"]', - // btnIUnderstand:'//button[text()="I Understand"]', - btnClick: `(//button[normalize-space(text())="{{btnclick}}"])`, - templatePencil:'(//button[contains(@class, "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200")])[2]', - publicTemplate:'(//span[@class="slider rounded-2xl"])[3]', - templateName:'//div//div//h3[@class="text-base font-medium"]', - modelApi:'//input[@id="OBOT_OPENAI_MODEL_PROVIDER_API_KEY"]', - modelSelect: `(//label[(text())="{{modelSelect}}"])`, - ValidateModel:'//button[@title="Select model for this thread"]', - gpt3btn:'//div[text()="gpt-3.5-turbo"]', - gpt4btn:'//div[text()="gpt-4"]', - message:'//label[text()="Your messages"]', - databaseOn:'//span[text()="Toggle Capability"]/../span[2]', - profileIcon:'//a[@id="navbar-home-link"]/following-sibling::button', - logOut:'//a[text()=" Log out"]', - cancelTool:'//span[text()=" AWS S3"]/following-sibling::button', - clickherelink:'//span[text()="click here"]', - signinHubspot:"//i18n-string[normalize-space()='Sign in to your HubSpot account']", - loginHubspotButton:'//i18n-string[text()="Log in"]', - emailHubspot:'//input[@type="email"]', - nextButton:'//button[@type="submit"]//i18n-string', - passwordHubspot:'//input[@type="password"]', - authentication: '//p[text()="Authentication is required."]', - // fillEmail: '//input[@placeholder="Email, phone, or Skype"]', - fillEmail: '//input[@autocomplete="username"]', - clickNext:'//input[@value="Next"]', - getCode:'//button[text()="Send code"]', - - MCP:{ - googlebtn: '//span[text()="Continue with Google"]', - emailInput: '//input[@type="email"]', - nextbtn: '//span[text()="Next"]', - passwordInput: '//input[@type="password"]', - cntBtn: '//span[text()="Continue"]', - navigationbtn: '//button[@class="icon-button z-20"]', - clickChatObot: '//button[normalize-space(text())="Chat"]', - connectorbtn: '//p[normalize-space(text())="Connectors"]/following-sibling::button', - mcpSearchInput: '//input[normalize-space(@placeholder)="Search by name..."]', - // selectMCPServer: '//p[normalize-space(text())="WordPress1"]', - selectMCPServer: (option: string) => `//p[normalize-space(text())="${option}"]/ancestor::div[contains(@class, 'flex')]/button`, - wpSiteURL: '//input[normalize-space(@id)="WORDPRESS_SITE"]', - wpUsername: '//input[normalize-space(@id)="WORDPRESS_USERNAME"]', - wpPassword: '//input[normalize-space(@id)="WordPress App Password"]', - btnClick: (option: string) => `//button[normalize-space(text())="${option}"]`, - promptInput: '//div[@class="plaintext-editor text-md relative w-full flex-1 grow resize-none p-2 leading-8 outline-none"]', - // submitPrompt: '//button[@type="submit"]', - // obotInput: '//div[@class="ProseMirror editor"]', - gitlabToken: '//input[@name="GitLab Personal Access Token"]', - // messageContainer: "//div[contains(@class, 'flex-1') and contains(@class, 'flex-col') and contains(@class, 'justify-start') and contains(@class, 'gap-8')]", - obotInput: "//div[contains(@class,'ProseMirror') and @contenteditable='true']", - submitPrompt: '//button[@type="submit"]', - lastBotReply: '//div[@class="message-content"]', - messageContainer: "//div[contains(@class, 'flex-1') and contains(@class, 'flex-col') and contains(@class, 'justify-start') and contains(@class, 'gap-8')]" - - } -} -export default Selectors; \ No newline at end of file + admin: { + oktaLogin: '//button[normalize-space(.//div) = "Sign in with Okta"]', + crateNewAgent: '//button[normalize-space(.//div) = "New Agent"]', + agentName: "(//div/child::input)[1]", + agentDes: "(//div/child::input)[2]", + tryItOutBtn: "//div/child::a", + editAgent: '(//a[contains(@href, "/admin/agents/")])[1]', + deleteAgent: + '(//a[contains(@href, "/admin/agents/")]/following-sibling::button)[1]', + confirmDelete: '//div[text()="Confirm"]', + agenetDeletedMessagge: + '//div[@data-content]//div[contains(text(), "Agent deleted")]', + memoryDropdown: + '//p[text()="Memory"]/ancestor::div[contains(@class, "flex")]/following-sibling::div//button', + setAlwaysOn: + '//div[contains(@role, "option")]/span[contains(text(),"Always On")]', + addTool: '//div[text()=" Add Tools"]', + addKnowledge: '//div[text()="Add Knowledge"]', + localFiles: '//div[text()="Local Files"]', + searchTool: '//input[@placeholder="Search tools..."]', + googleSearch: '//span[text()="Google Search "]', + weather: '//span[text()="Weather "]', + viewAllMemories: '//button[text()="View All Memories"]', + advanced: '//h4[text()="Advanced"]', + labelClick: `(//label[normalize-space(text())="{{labelClick}}"])`, + userleftsideMenu: '//span[text()="Users"]', + userFilter: '//span[text()="User"]', + searchText: '//input[@placeholder="Filter..."]', + clickonthreedotBtn: + '//div[@class="flex justify-end"]//button[@type="button"]', + updateroleBtn: '//div[text()="Update Role"]', + clickonroleDrp: '//button[@aria-controls="radix-:rceg:"]', + selectRole: '//span[text()="{{role}}"]', + updateBtn: '//div[text()="Update"]', + deleteroleBtn: '//div[text()="Delete User"]', + deleteBtn: '//div[text()="Delete"]', + chatthreadMenu: '//a//span[text()="Chat Threads"]', + }, + agentDes: '//p[@class="text-gray w-sm font-light md:w-md"]', + authLink: + '//a[contains(@class, "bg-blue") and contains(@class, "rounded-3xl") and contains(@class, "text-white")]', + scheduleDay: '(//button[normalize-space(text())="daily"])[1]', + scheduleTime: '(//button[normalize-space(text())="on the hour"])[1]', + homeButton: '//a[@id="navbar-home-link"]', + continueBtn: + '//button[contains(text(), "I dunno")]/following-sibling::button', + lunchAgent: '//button[normalize-space(text())="Launch Agent"]', + obotEditArea: '//button[@id="edit-basic-details-button"]', + editAreaPencil: '(//button[@id="edit-basic-details-button"]/child::div)[2]', + editAgentName: '//input[@id="project-name"]', + obotNav: "(//div/child::span/following-sibling::button)[1]", + closeEditArea: '//button[@class="icon-button absolute top-2 right-2"]', + addKnowledge: '//p[text()="Knowledge"]/following-sibling::div', + clicksNewThread: '//a[.//img[@alt="Obot icon"]]/following-sibling::button', + memoryValidate: "//tr//td[2]", + refreashMemory: '//span[text()="Most recent memories"]/../div', + memoryIcon: + '//button[@data-memories-btn and contains(@class, "icon-button")]', + closeMemory: '(//button[contains(@class, "absolute top-0 right-0 p-3")])[1]', + deleteAllMemory: '(//div//button[contains(text()," Delete All")])[2]', + deleteMemoryValidate: '//p[text()="No memories stored"]', + startFromScratch: '//button[text()=" Create From Scratch"]', + configuration: '//span[text()="Configuration "]', + editObotName: '//span[text()="Name & Description "]', + mcpServer: '//span[text()="MCP Servers "]', + addMCPServer: '//button[text()=" Add MCP Server"]', + searchMCPTool: '//input[@placeholder="Search MCP Servers..."]', + selectedTool: `(//h4[normalize-space(text())="{{mcpTool}}"])`, + mcpStep: `(//button[(text())="{{mcpStep}}"])`, + selectServer: '(//button[text()="Select Server "])', + addServer: '//button[text()="Add server"]', + validateMCPTool: (option: string) => + `//p[normalize-space(text())="${option}"]`, + knowledge: '//span[text()="Knowledge "]', + selectFromSidebar: `(//span[(text())="{{select}}"])`, + enableChatBot: '//h5[text()="Enable"]/../label', + copyTemplates: + '//div/a[@class="overflow-hidden text-sm text-ellipsis hover:underline"]', + copyChatBot: + '//div/a[@class="overflow-hidden text-ellipsis hover:underline"]', + validateThread: '//button[text()="New Thread"]', + // btnIUnderstand:'//button[text()="I Understand"]', + btnClick: `(//button[normalize-space(text())="{{btnclick}}"])`, + templatePencil: + '(//button[contains(@class, "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200")])[2]', + publicTemplate: '(//span[@class="slider rounded-2xl"])[3]', + templateName: '//div//div//h3[@class="text-base font-medium"]', + modelApi: '//input[@id="OBOT_OPENAI_MODEL_PROVIDER_API_KEY"]', + modelSelect: `(//label[(text())="{{modelSelect}}"])`, + ValidateModel: '//button[@title="Select model for this thread"]', + gpt3btn: '//div[text()="gpt-3.5-turbo"]', + gpt4btn: '//div[text()="gpt-4"]', + message: '//label[text()="Your messages"]', + databaseOn: '//span[text()="Toggle Capability"]/../span[2]', + profileIcon: '//a[@id="navbar-home-link"]/following-sibling::button', + logOut: '//a[text()=" Log out"]', + cancelTool: '//span[text()=" AWS S3"]/following-sibling::button', + clickherelink: '//span[text()="click here"]', + signinHubspot: + "//i18n-string[normalize-space()='Sign in to your HubSpot account']", + loginHubspotButton: '//i18n-string[text()="Log in"]', + emailHubspot: '//input[@type="email"]', + nextButton: '//button[@type="submit"]//i18n-string', + passwordHubspot: '//input[@type="password"]', + authentication: '//p[text()="Authentication is required."]', + // fillEmail: '//input[@placeholder="Email, phone, or Skype"]', + fillEmail: '//input[@autocomplete="username"]', + clickNext: '//input[@value="Next"]', + getCode: '//button[text()="Send code"]', + + MCP: { + googlebtn: '//span[text()="Continue with Google"]', + emailInput: '//input[@type="email"]', + nextbtn: '//span[text()="Next"]', + passwordInput: '//input[@type="password"]', + cntBtn: '//span[text()="Continue"]', + navigationbtn: '//button[@class="icon-button z-20"]', + clickChatObot: '//button[normalize-space(text())="Chat"]', + connectorbtn: + '//p[normalize-space(text())="Connectors"]/following-sibling::button', + mcpSearchInput: + '//input[normalize-space(@placeholder)="Search by name..."]', + // selectMCPServer: '//p[normalize-space(text())="WordPress"]', + selectMCPServer: (option: string) => + `//p[normalize-space(text())="${option}"]/ancestor::div[contains(@class, 'flex')]/button`, + wpSiteURL: '//input[normalize-space(@id)="WORDPRESS_SITE"]', + wpUsername: '//input[normalize-space(@id)="WORDPRESS_USERNAME"]', + wpPassword: '//input[normalize-space(@id)="WordPress App Password"]', + btnClick: (option: string) => + `//button[normalize-space(text())="${option}"]`, + promptInput: + '//div[@class="plaintext-editor text-md relative w-full flex-1 grow resize-none p-2 leading-8 outline-none"]', + // submitPrompt: '//button[@type="submit"]', + // obotInput: '//div[@class="ProseMirror editor"]', + gitlabToken: '//input[@name="GitLab Personal Access Token"]', + // messageContainer: "//div[contains(@class, 'flex-1') and contains(@class, 'flex-col') and contains(@class, 'justify-start') and contains(@class, 'gap-8')]", + obotInput: + "//div[contains(@class,'ProseMirror') and @contenteditable='true']", + submitPrompt: '//button[@type="submit"]', + lastBotReply: '//div[@class="message-content"]', + messageContainer: + "//div[contains(@class, 'flex-1') and contains(@class, 'flex-col') and contains(@class, 'justify-start') and contains(@class, 'gap-8')]", + }, +}; +export default Selectors; diff --git a/src/features/mcpServer.feature b/src/features/mcpServer.feature index 20459b5..de75661 100644 --- a/src/features/mcpServer.feature +++ b/src/features/mcpServer.feature @@ -1,22 +1,24 @@ -Feature: Connecte MCP server on Obot +Feature: Connect MCP server on Obot Background: Navigate to Obot Given I setup context for assertion When User navigates the Obot main login page Then User open chat Obot + @wordpress Scenario: Validate Wordpress sequential prompts on Obot When User open MCP connector page And User select "WordPress" MCP server And User select "Connect To Server" button - And User connect to the WordPress1 MCP server + And User connect to the WordPress MCP server When User sends prompts to Obot AI chat for "Wordpress" MCP server Then All prompts results should be validated and report generated for selected "Wordpress" MCP Server - Scenario: Validate GitLab sequential prompts on Obot + @gitlab + Scenario: Validate GitLab sequential prompts on Obot When User open MCP connector page And User select "GitLab" MCP server And User select "Connect To Server" button And User connect to the GitLab MCP server When User sends prompts to Obot AI chat for "Gitlab" MCP server - Then All prompts results should be validated and report generated for selected "Gitlab" MCP Server \ No newline at end of file + Then All prompts results should be validated and report generated for selected "Gitlab" MCP Server diff --git a/src/steps/mcpServer.step.ts b/src/steps/mcpServer.step.ts index 86e5bd0..438dda6 100644 --- a/src/steps/mcpServer.step.ts +++ b/src/steps/mcpServer.step.ts @@ -1,91 +1,118 @@ import { When, Then, Given } from "@wdio/cucumber-framework"; import Selectors from "../core/selectors"; -import { clickToElement,isElementDisplayed,slowInputFilling} from "../core/func"; +import { + clickToElement, + isElementDisplayed, + slowInputFilling, +} from "../core/func"; import { LONG_PAUSE, SHORT_PAUSE } from "../core/timeouts"; -import { aggregateToolResponses, saveMCPReport, sendPromptValidateAndCollect } from "../core/mcpFunc"; -import path from 'path'; -import { promises as fs } from 'fs'; +import { + aggregateToolResponses, + saveMCPReport, + sendPromptValidateAndCollect, +} from "../core/mcpFunc"; +import path from "path"; +import { promises as fs } from "fs"; -Given(/^User navigates the Obot main login page$/, async() => { - const url = process.env.OBOT_URL ; - await browser.url(url); +Given(/^User navigates the Obot main login page$/, async () => { + const url = process.env.OBOT_URL; + await browser.url(url); }); Then(/^User open chat Obot$/, async () => { - await clickToElement(Selectors.MCP.navigationbtn); - await clickToElement(Selectors.MCP.clickChatObot); + await clickToElement(Selectors.MCP.navigationbtn); + await clickToElement(Selectors.MCP.clickChatObot); }); When(/^User open MCP connector page$/, async () => { - await clickToElement(Selectors.MCP.connectorbtn); + await clickToElement(Selectors.MCP.connectorbtn); }); Then(/^User select "([^"]*)" MCP server$/, async (MCPServer) => { - await slowInputFilling(Selectors.MCP.mcpSearchInput, MCPServer); - await isElementDisplayed(Selectors.MCP.selectMCPServer(MCPServer), LONG_PAUSE); - // Wait until matching elements appear - const allServers = await $$(Selectors.MCP.selectMCPServer(MCPServer)); - if (await allServers.length === 0) throw new Error(`No MCP server found matching: ${MCPServer}`); + await slowInputFilling(Selectors.MCP.mcpSearchInput, MCPServer); + await isElementDisplayed( + Selectors.MCP.selectMCPServer(MCPServer), + LONG_PAUSE, + ); + // Wait until matching elements appear + const allServers = await $$(Selectors.MCP.selectMCPServer(MCPServer)); + if ((await allServers.length) === 0) + throw new Error(`No MCP server found matching: ${MCPServer}`); - // Click the last one - const lastServer = allServers[await allServers.length - 1]; - await lastServer.waitForDisplayed({ timeout: LONG_PAUSE }); - await lastServer.click(); + // Click the last one + const lastServer = allServers[(await allServers.length) - 1]; + await lastServer.waitForDisplayed({ timeout: LONG_PAUSE }); + await lastServer.click(); - await browser.pause(SHORT_PAUSE); + await browser.pause(SHORT_PAUSE); }); Then(/^User select "([^"]*)" button$/, async (Button) => { - await isElementDisplayed(Selectors.MCP.btnClick(Button),SHORT_PAUSE); - await clickToElement(Selectors.MCP.btnClick(Button)); + await isElementDisplayed(Selectors.MCP.btnClick(Button), SHORT_PAUSE); + await clickToElement(Selectors.MCP.btnClick(Button)); }); -Then(/^User connect to the WordPress1 MCP server$/, async () => { - await slowInputFilling(Selectors.MCP.wpSiteURL,process.env.WP_URL); - await slowInputFilling(Selectors.MCP.wpUsername,process.env.WP_USERNAME); - await slowInputFilling(Selectors.MCP.wpPassword, process.env.WP_PASSWORD); - await clickToElement(Selectors.MCP.btnClick("Launch")); - await browser.pause(LONG_PAUSE*2); +Then(/^User connect to the WordPress MCP server$/, async () => { + await slowInputFilling(Selectors.MCP.wpSiteURL, process.env.WP_URL); + await slowInputFilling(Selectors.MCP.wpUsername, process.env.WP_USERNAME); + await slowInputFilling(Selectors.MCP.wpPassword, process.env.WP_PASSWORD); + await clickToElement(Selectors.MCP.btnClick("Launch")); + await browser.pause(LONG_PAUSE * 2); }); - + Then(/^User asks obot "([^"]*)"$/, async (prompt) => { - await slowInputFilling(Selectors.MCP.obotInput, prompt); - await clickToElement(Selectors.MCP.submitPrompt); - await browser.pause(LONG_PAUSE); + await slowInputFilling(Selectors.MCP.obotInput, prompt); + await clickToElement(Selectors.MCP.submitPrompt); + await browser.pause(LONG_PAUSE); }); Then(/^User connect to the GitLab MCP server$/, async () => { - await slowInputFilling(Selectors.MCP.gitlabToken,process.env.GITLAB_TOKEN); - await clickToElement(Selectors.MCP.btnClick("Launch")); - await browser.pause(LONG_PAUSE); + await slowInputFilling(Selectors.MCP.gitlabToken, process.env.GITLAB_TOKEN); + await clickToElement(Selectors.MCP.btnClick("Launch")); + await browser.pause(LONG_PAUSE); }); -When(/^User sends prompts to Obot AI chat for "([^"]*)" MCP server$/, { timeout: 15 * 60 * 1000 }, async function(serverName: string) { - const jsonPath = path.resolve(process.cwd(), 'src', 'data', `${serverName.toLowerCase()}.MCP.json`); - const data = await fs.readFile(jsonPath, 'utf-8'); - const { prompts, tools } = JSON.parse(data); +When( + /^User sends prompts to Obot AI chat for "([^"]*)" MCP server$/, + { timeout: 15 * 60 * 1000 }, + async function (serverName: string) { + const jsonPath = path.resolve( + process.cwd(), + "src", + "data", + `${serverName.toLowerCase()}.MCP.json`, + ); + const data = await fs.readFile(jsonPath, "utf-8"); + const { prompts, tools } = JSON.parse(data); - this.promptResults = []; - const toolList = tools + this.promptResults = []; + const toolList = tools; - for (let i = 0; i < prompts.length; i++) { - try { - const result = await sendPromptValidateAndCollect(prompts[i], toolList, i); - this.promptResults.push(result); - } catch (err: any) { - console.error(`Error in prompt #${i+1}: ${err.message}`); - this.promptResults.push({ prompt: prompts[i], error: err.message }); + for (let i = 0; i < prompts.length; i++) { + try { + const result = await sendPromptValidateAndCollect( + prompts[i], + toolList, + i, + ); + this.promptResults.push(result); + } catch (err: any) { + console.error(`Error in prompt #${i + 1}: ${err.message}`); + this.promptResults.push({ prompt: prompts[i], error: err.message }); + } } - } -}); + }, +); -Then(/^All prompts results should be validated and report generated for selected "([^"]*)" MCP Server$/, async function(serverName: string) { - const report = aggregateToolResponses(this.promptResults); - saveMCPReport(serverName, report); - - const errors = this.promptResults.filter(r => r.error); - if (errors.length > 0) { - console.warn(`${errors.length} prompts had issues.`); - } -}); +Then( + /^All prompts results should be validated and report generated for selected "([^"]*)" MCP Server$/, + async function (serverName: string) { + const report = aggregateToolResponses(this.promptResults); + saveMCPReport(serverName, report); + const errors = this.promptResults.filter((r) => r.error); + if (errors.length > 0) { + console.warn(`${errors.length} prompts had issues.`); + } + }, +); From aa93dabfe1f9a750904648d93f921c688f3d6d74 Mon Sep 17 00:00:00 2001 From: Taylor Price Date: Thu, 20 Nov 2025 11:08:23 -0700 Subject: [PATCH 3/4] fix: replace function does not exist Signed-off-by: Taylor Price --- .github/workflows/pr-trigger.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-trigger.yml b/.github/workflows/pr-trigger.yml index 9c2f464..cde6990 100644 --- a/.github/workflows/pr-trigger.yml +++ b/.github/workflows/pr-trigger.yml @@ -82,12 +82,19 @@ jobs: sleep 5 done + - name: Remove file extension + env: + MATRIX_SERVER_FILE: ${{ matrix.server.file }} + run: | + MCP_SERVER_NAME="${MATRIX_SERVER_FILE/.yaml/}" + echo "MCP_SERVER_NAME=$MCP_SERVER_NAME" >> $GITHUB_ENV + - name: Create catalog entry run: | curl localhost:8080/api/mcp-catalogs/default/entries -X POST -H "Content-Type: application/json" \ -d '{ - "name": "test-${{ replace(matrix.server.file, '.yaml', '') }}", - "description": "testing ${{ replace(matrix.server.file, '.yaml', '') }} mcp", + "name": "test-${MCP_SERVER_NAME}", + "description": "testing ${MCP_SERVER_NAME} mcp", "icon": "https://avatars.githubusercontent.com/u/9919?v=4", "repoURL": "https://github.com/testing/testing", "runtime": "containerized", @@ -114,4 +121,4 @@ jobs: GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY}} run: | - npx wdio run wdio.conf.ts --cucumberOpts.tagExpression='@${{ replace(matrix.server.file, '.yaml', '') }}' + npx wdio run wdio.conf.ts --cucumberOpts.tagExpression='@${MCP_SERVER_NAME}' From d88daf6210e131483cb4614377aee21198dcafc2 Mon Sep 17 00:00:00 2001 From: Taylor Price Date: Thu, 20 Nov 2025 11:30:23 -0700 Subject: [PATCH 4/4] fix: close escape sequence Signed-off-by: Taylor Price --- .github/workflows/pr-trigger.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-trigger.yml b/.github/workflows/pr-trigger.yml index cde6990..278297a 100644 --- a/.github/workflows/pr-trigger.yml +++ b/.github/workflows/pr-trigger.yml @@ -102,7 +102,7 @@ jobs: "image": "${{ matrix.server.containerizedConfig.image }}", "port": ${{ matrix.server.containerizedConfig.port }}, "path": "${{ matrix.server.containerizedConfig.path }}", - "args": ${{ toJson(matrix.server.containerizedConfig.args) } + "args": ${{ toJson(matrix.server.containerizedConfig.args) }} }, "metadata": { "categories": "testing" @@ -119,6 +119,6 @@ jobs: WP_PASSWORD: ${{ secrets.WP_PASSWORD }} OBOT_URL: ${{ secrets.OBOT_URL }} GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY}} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | npx wdio run wdio.conf.ts --cucumberOpts.tagExpression='@${MCP_SERVER_NAME}'