From 97de73f20aa64764ec294f6bc008691dd45c4c5a Mon Sep 17 00:00:00 2001 From: Isuru Weerarathna Date: Mon, 7 Oct 2024 13:58:26 +0530 Subject: [PATCH 1/2] Support named path parameters for websocket routes #1495 (#1497) * Support named path parameters for websocket routes #1495 * fix pr comments #1495 --- packages/commons-server/package-lock.json | 70 +++--------- packages/commons-server/package.json | 1 + packages/commons-server/src/libs/requests.ts | 18 +++- .../commons-server/src/libs/server/server.ts | 20 ++-- .../test/suites/requests.spec.ts | 101 +++++++++++++----- .../test/suites/server/ws.spec.ts | 7 +- .../environment-routes.component.html | 2 +- .../route-response-rules.component.html | 6 +- .../route-response-rules.component.ts | 3 - .../renderer/app/constants/texts.constant.ts | 2 +- 10 files changed, 124 insertions(+), 106 deletions(-) diff --git a/packages/commons-server/package-lock.json b/packages/commons-server/package-lock.json index 9f0acb2c8..55545e542 100644 --- a/packages/commons-server/package-lock.json +++ b/packages/commons-server/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@apidevtools/swagger-parser": "10.1.0", "@faker-js/faker": "8.4.1", - "@mockoon/commons": "8.4.0", "append-field": "1.0.0", "busboy": "1.6.0", "cookie-parser": "1.4.6", @@ -23,6 +22,7 @@ "killable": "1.0.1", "mime-types": "2.1.35", "object-path": "0.11.8", + "path-to-regexp": "7.1.0", "qs": "6.12.3", "range-parser": "1.2.1", "typed-emitter": "2.1.0", @@ -261,19 +261,6 @@ "npm": ">=6.14.13" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -336,17 +323,6 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, - "node_modules/@mockoon/commons": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@mockoon/commons/-/commons-8.4.0.tgz", - "integrity": "sha512-4rJE2l+NqmZWezcXXTOGH76v/y+qzKJbji0v+MxJPZWvaPlH/iNpUSTzky/9qerldS1SVgqhe5tCfbMfVwR8Hg==", - "dependencies": { - "joi": "17.13.3" - }, - "funding": { - "url": "https://mockoon.com/sponsor-us/" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -394,24 +370,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -1917,6 +1875,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + }, "node_modules/express/node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -2707,18 +2670,6 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -3310,9 +3261,12 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-7.1.0.tgz", + "integrity": "sha512-ZToe+MbUF4lBqk6dV8GKot4DKfzrxXsplOddH8zN3YK+qw9/McvP7+4ICjZvOne0jQhN4eJwHsX6tT0Ns19fvw==", + "engines": { + "node": ">=16" + } }, "node_modules/path-type": { "version": "4.0.0", diff --git a/packages/commons-server/package.json b/packages/commons-server/package.json index 8f165ebb0..2f8132890 100644 --- a/packages/commons-server/package.json +++ b/packages/commons-server/package.json @@ -46,6 +46,7 @@ "killable": "1.0.1", "mime-types": "2.1.35", "object-path": "0.11.8", + "path-to-regexp": "7.1.0", "qs": "6.12.3", "range-parser": "1.2.1", "typed-emitter": "2.1.0", diff --git a/packages/commons-server/src/libs/requests.ts b/packages/commons-server/src/libs/requests.ts index 2ad298615..e466d327f 100644 --- a/packages/commons-server/src/libs/requests.ts +++ b/packages/commons-server/src/libs/requests.ts @@ -1,6 +1,8 @@ import { Request } from 'express'; import { IncomingMessage } from 'http'; +import { match } from 'path-to-regexp'; +import { CloneObject, Route } from '@mockoon/commons'; import { parse as parseUrl } from 'url'; import { parseRequestMessage, parseWebSocketMessage } from './utils'; @@ -92,10 +94,22 @@ export const fromExpressRequest = (req: Request): ServerRequest => */ export const fromWsRequest = ( req: IncomingMessage, + originalRoute: Route, message?: string ): ServerRequest => { const location = parseUrl(req.url || '', true); + let pathParams = {}; + const urlPathMatchFn = match( + originalRoute.endpoint.startsWith('/') + ? originalRoute.endpoint + : '/' + originalRoute.endpoint + ); + const result = urlPathMatchFn(location.pathname || ''); + if (result) { + pathParams = result.params || {}; + } + const structuredMessage = message ? parseWebSocketMessage(message || '', req) : undefined; @@ -111,8 +125,8 @@ export const fromWsRequest = ( req.socket?.remoteAddress, method: req.method, originalRequest: req, - params: {}, - query: JSON.parse(JSON.stringify(location.query)), + params: CloneObject(pathParams), + query: CloneObject(location.query), stringBody: message || toString(req.body) || '' } as ServerRequest; }; diff --git a/packages/commons-server/src/libs/server/server.ts b/packages/commons-server/src/libs/server/server.ts index f3f2dfd39..25c845d10 100644 --- a/packages/commons-server/src/libs/server/server.ts +++ b/packages/commons-server/src/libs/server/server.ts @@ -48,6 +48,7 @@ import { import killable from 'killable'; import { lookup as mimeTypeLookup } from 'mime-types'; import { basename, extname } from 'path'; +import { match } from 'path-to-regexp'; import { parse as qsParse } from 'qs'; import rangeParser from 'range-parser'; import { Readable } from 'stream'; @@ -615,7 +616,7 @@ export class MockoonServer extends (EventEmitter as new () => TypedEmitter { const webSocketServer = new WebSocket.Server({ noServer: true, - path: `${envPath}/${wsRoute.endpoint}` + path: `${envPath}` }); this.webSocketServers.push(webSocketServer); @@ -625,10 +626,15 @@ export class MockoonServer extends (EventEmitter as new () => TypedEmitter { - // const urlParsed = parseUrl(req.url || '', true); - if (urlParsed.pathname === `${envPath}/${wsRoute.endpoint}`) { + if (pathMatcherFn(urlParsed.pathname || '')) { webSocketServer.handleUpgrade(req, socket, head, (client) => { webSocketServer.emit('connection', client, req); }); @@ -700,7 +706,7 @@ export class MockoonServer extends (EventEmitter as new () => TypedEmitter TypedEmitter TypedEmitter { const enabledRouteResponse = new ResponseRulesInterpreter( route.responses, - fromWsRequest(request), + fromWsRequest(request, route), route.responseMode, this.environment, this.processedDatabuckets, @@ -2180,7 +2186,7 @@ export class MockoonServer extends (EventEmitter as new () => TypedEmitter match[1]); + return [...(matches || [])].map((mtc) => mtc[1]); } /** diff --git a/packages/commons-server/test/suites/requests.spec.ts b/packages/commons-server/test/suites/requests.spec.ts index ae4be1766..c55908cd0 100644 --- a/packages/commons-server/test/suites/requests.spec.ts +++ b/packages/commons-server/test/suites/requests.spec.ts @@ -1,3 +1,4 @@ +import { Route } from '@mockoon/commons'; import { deepStrictEqual, notStrictEqual, strictEqual } from 'assert'; import { Request } from 'express'; import { ParamsDictionary, Query } from 'express-serve-static-core'; @@ -41,7 +42,7 @@ describe('Requests', () => { it('should return cookies correctly', () => { const req = { - cookies: { 'session-id': 'abc' } + cookies: { 'session-id': 'abc' } as any } as Request; const result = fromExpressRequest(req); deepStrictEqual(result.cookies, { 'session-id': 'abc' }); @@ -107,18 +108,36 @@ describe('Requests', () => { const req = { url: 'api/path1/test?q=1&p=abc' } as IncomingMessage; - const result = fromWsRequest(req); + + const result = fromWsRequest(req, { + endpoint: 'api/path1/test' + } as Route); deepStrictEqual(result.query, { q: '1', p: 'abc' }); deepStrictEqual(result.params, {}); strictEqual(result.originalRequest, req); }); + it('should parse url path variables correctly from url', () => { + const req = { + url: '/api/path1/test?q=1&p=abc' + } as IncomingMessage; + + const result = fromWsRequest(req, { + endpoint: 'api/:var1/:var2' + } as Route); + deepStrictEqual(result.query, { q: '1', p: 'abc' }); + deepStrictEqual(result.params, { var1: 'path1', var2: 'test' }); + strictEqual(result.originalRequest, req); + }); + it('should return body and stringBody correctly when message is not given', () => { const req = { url: 'api/path1/test?q=1&p=abc', body: { a: 1, text: 'hello' } } as IncomingMessage; - const result = fromWsRequest(req); + const result = fromWsRequest(req, { + endpoint: 'api/path1/test' + } as Route); deepStrictEqual(result.body, { a: 1, text: 'hello' }); strictEqual(result.stringBody, JSON.stringify({ a: 1, text: 'hello' })); }); @@ -133,6 +152,9 @@ describe('Requests', () => { } as IncomingMessage; const result = fromWsRequest( req, + { + endpoint: 'api/path1/test' + } as Route, JSON.stringify({ b: 2, hello: 'world' }) ); deepStrictEqual(result.body, { b: 2, hello: 'world' }); @@ -147,7 +169,9 @@ describe('Requests', () => { accept: 'text/html' } as NodeJS.Dict } as IncomingMessage; - const result = fromWsRequest(req); + const result = fromWsRequest(req, { + endpoint: 'api/path1/test' + } as Route); strictEqual(result.header('content-type'), 'application/json'); strictEqual(result.get('accept'), 'text/html'); strictEqual(result.get('non-existence-header'), undefined); @@ -164,7 +188,9 @@ describe('Requests', () => { 'x-forwarded-for': '192.168.1.1' } as NodeJS.Dict } as IncomingMessage; - const result = fromWsRequest(req); + const result = fromWsRequest(req, { + endpoint: 'api/path1/test' + } as Route); strictEqual(result.hostname, 'localhost'); strictEqual(result.ip, '192.168.1.1'); }); @@ -175,7 +201,9 @@ describe('Requests', () => { cookie: 'a=1;b=hello%20this%20is%20%2521%3D4' } } as IncomingMessage; - const result = fromWsRequest(req); + const result = fromWsRequest(req, { + endpoint: 'api/path1/test' + } as Route); deepStrictEqual(result.cookies, { a: '1', b: 'hello this is %21=4' @@ -191,7 +219,9 @@ describe('Requests', () => { 'content-type': 'application/json' } as NodeJS.Dict } as IncomingMessage; - const req = fromWsRequest(originalReq); + const req = fromWsRequest(originalReq, { + endpoint: 'api/path1/test' + } as Route); const result = fromServerRequest( req, JSON.stringify({ b: 2, hello: 'world' }) @@ -201,12 +231,17 @@ describe('Requests', () => { }); it('should return empty when no message or no body is specified', () => { - const req = fromWsRequest({ - url: 'api/path1/test?q=1&p=abc', - headers: { - 'content-type': 'application/json' - } as NodeJS.Dict - } as IncomingMessage); + const req = fromWsRequest( + { + url: 'api/path1/test?q=1&p=abc', + headers: { + 'content-type': 'application/json' + } as NodeJS.Dict + } as IncomingMessage, + { + endpoint: 'api/path1/test' + } as Route + ); const result = fromServerRequest(req); strictEqual(result.body, undefined); @@ -214,13 +249,18 @@ describe('Requests', () => { }); it('should update body and stringBody correctly when parsing from existing request', () => { - const req = fromWsRequest({ - url: 'api/path1/test?q=1&p=abc', - body: { a: 1, text: 'hello' }, - headers: { - 'content-type': 'application/json' - } as NodeJS.Dict - } as IncomingMessage); + const req = fromWsRequest( + { + url: 'api/path1/test?q=1&p=abc', + body: { a: 1, text: 'hello' }, + headers: { + 'content-type': 'application/json' + } as NodeJS.Dict + } as IncomingMessage, + { + endpoint: 'api/path1/test' + } as Route + ); const result = fromServerRequest( req, JSON.stringify({ b: 2, hello: 'world' }) @@ -236,14 +276,19 @@ describe('Requests', () => { }); it('should fallback to original body and stringBody is message is not given', () => { - const req = fromWsRequest({ - url: 'api/path1/test?q=1&p=abc', - body: { a: 1, text: 'hello' }, - stringBody: JSON.stringify({ a: 1, text: 'hello' }), - headers: { - 'content-type': 'application/json' - } as NodeJS.Dict - } as IncomingMessage); + const req = fromWsRequest( + { + url: 'api/path1/test?q=1&p=abc', + body: { a: 1, text: 'hello' }, + stringBody: JSON.stringify({ a: 1, text: 'hello' }), + headers: { + 'content-type': 'application/json' + } as NodeJS.Dict + } as IncomingMessage, + { + endpoint: 'api/path1/test' + } as Route + ); const result = fromServerRequest(req); deepStrictEqual(result.body, { a: 1, text: 'hello' }); diff --git a/packages/commons-server/test/suites/server/ws.spec.ts b/packages/commons-server/test/suites/server/ws.spec.ts index 74e1b8c45..1829ba802 100644 --- a/packages/commons-server/test/suites/server/ws.spec.ts +++ b/packages/commons-server/test/suites/server/ws.spec.ts @@ -31,7 +31,12 @@ const EMPTY_SERVER_CTX = { envVarPrefix: '' } as ServerContext; -const EMPTY_WS_REQUEST = fromWsRequest({} as IncomingMessage); +const EMPTY_WS_REQUEST = fromWsRequest( + {} as IncomingMessage, + { + endpoint: 'api/path1/test' + } as Route +); describe('Minimum Streaming Interval', () => { it('minimum interval should not lower than 10ms', () => { diff --git a/packages/desktop/src/renderer/app/components/environment-routes/environment-routes.component.html b/packages/desktop/src/renderer/app/components/environment-routes/environment-routes.component.html index 08cde26f7..368ac4fcc 100644 --- a/packages/desktop/src/renderer/app/components/environment-routes/environment-routes.component.html +++ b/packages/desktop/src/renderer/app/components/environment-routes/environment-routes.component.html @@ -406,7 +406,7 @@ *ngIf=" rulesDisablingResponseModes.includes( data.activeRoute?.responseMode - ) || !!data.activeRoute.streamingMode + ) || data.activeRoute.streamingMode === 'BROADCAST' " [ngbTooltip]="texts.DISABLED_RULES_WARNING" id="disabled-rules-warning-icon" diff --git a/packages/desktop/src/renderer/app/components/route-response-rules/route-response-rules.component.html b/packages/desktop/src/renderer/app/components/route-response-rules/route-response-rules.component.html index 28a57c381..7a58091d8 100644 --- a/packages/desktop/src/renderer/app/components/route-response-rules/route-response-rules.component.html +++ b/packages/desktop/src/renderer/app/components/route-response-rules/route-response-rules.component.html @@ -44,11 +44,7 @@ formControlName="target" [enableCustomInput]="false" [hasCategory]="true" - [items]=" - activeRoute.type === 'ws' - ? webSocketResponseRuleTargets - : responseRuleTargets - " + [items]="responseRuleTargets" [dropdownId]="'rules' + ruleIndex + 'target'" > diff --git a/packages/desktop/src/renderer/app/components/route-response-rules/route-response-rules.component.ts b/packages/desktop/src/renderer/app/components/route-response-rules/route-response-rules.component.ts index c230a3ffc..b7defec83 100644 --- a/packages/desktop/src/renderer/app/components/route-response-rules/route-response-rules.component.ts +++ b/packages/desktop/src/renderer/app/components/route-response-rules/route-response-rules.component.ts @@ -64,9 +64,6 @@ export class RouteResponseRulesComponent implements OnInit, OnDestroy { { value: 'global_var', label: 'Global variable' }, { value: 'data_bucket', label: 'Data bucket' } ]; - public webSocketResponseRuleTargets = this.responseRuleTargets.filter( - (rt) => !['cookie', 'params'].includes(rt.value) - ); public responseRuleOperators: DropdownItems = [ { value: 'equals', label: 'equals' }, { value: 'regex', label: 'regex' }, diff --git a/packages/desktop/src/renderer/app/constants/texts.constant.ts b/packages/desktop/src/renderer/app/constants/texts.constant.ts index 1546ca954..813b1d0e7 100644 --- a/packages/desktop/src/renderer/app/constants/texts.constant.ts +++ b/packages/desktop/src/renderer/app/constants/texts.constant.ts @@ -1,6 +1,6 @@ export const Texts = { DISABLED_RULES_WARNING: - 'Rules disabled by one of the response modes (sequential, random, rules disabled) or when in streaming mode', + 'Rules disabled by one of the response modes (sequential, random, rules disabled) or when in broadcast streaming mode', MINIMUM_STREAMING_INTERVAL_WARNING: 'Minimum streaming interval should be 10ms' }; From e0a9e5eb757425ae245c6ddf5054872f2544cda7 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 7 Oct 2024 13:57:11 +0200 Subject: [PATCH 2/2] Add a responses counter (#1539) Closes #1528 --- .../environment-routes.component.html | 65 +++++++++++-------- .../renderer/styles/boostrap-overrides.scss | 6 ++ packages/desktop/test/libs/routes.ts | 5 +- packages/desktop/test/libs/utils.ts | 7 ++ 4 files changed, 55 insertions(+), 28 deletions(-) diff --git a/packages/desktop/src/renderer/app/components/environment-routes/environment-routes.component.html b/packages/desktop/src/renderer/app/components/environment-routes/environment-routes.component.html index 368ac4fcc..5a53b2764 100644 --- a/packages/desktop/src/renderer/app/components/environment-routes/environment-routes.component.html +++ b/packages/desktop/src/renderer/app/components/environment-routes/environment-routes.component.html @@ -182,33 +182,44 @@ *ngIf="data.activeRouteResponse" class="dropdown-toggle-label" > - Response {{ data.activeRouteResponseIndex + 1 }} - - Response {{ data.activeRouteResponseIndex + 1 }} ({{ - data.activeRouteResponse?.statusCode - }}) - - Response {{ data.activeRouteResponseIndex }} ({{ - data.activeRouteResponse?.statusCode - }}) - - CRUD operations - - {{ data.activeRouteResponse?.label }} +
+
+ Response {{ data.activeRouteResponseIndex + 1 }} + + Response {{ data.activeRouteResponseIndex + 1 }} ({{ + data.activeRouteResponse?.statusCode + }}) + + Response {{ data.activeRouteResponseIndex }} ({{ + data.activeRouteResponse?.statusCode + }}) + + CRUD operations + + {{ data.activeRouteResponse?.label }} +
+
+ {{ data.activeRoute?.responses.length }} +
+
diff --git a/packages/desktop/src/renderer/styles/boostrap-overrides.scss b/packages/desktop/src/renderer/styles/boostrap-overrides.scss index c39c7a109..99da86fc1 100644 --- a/packages/desktop/src/renderer/styles/boostrap-overrides.scss +++ b/packages/desktop/src/renderer/styles/boostrap-overrides.scss @@ -105,6 +105,12 @@ label { } } +// remove bootstrap fix for badges in buttons (it's not working when using flex) +.btn .badge { + position: initial; + top: 0; +} + .btn.btn-icon { text-decoration: none; diff --git a/packages/desktop/test/libs/routes.ts b/packages/desktop/test/libs/routes.ts index 3809b7cb7..691ae0fc3 100644 --- a/packages/desktop/test/libs/routes.ts +++ b/packages/desktop/test/libs/routes.ts @@ -478,7 +478,10 @@ class Routes { } public async assertSelectedRouteResponseLabel(expected: string) { - await utils.assertElementText(this.routeResponseDropdownlabel, expected); + await utils.assertElementTextContains( + this.routeResponseDropdownlabel, + expected + ); } public async assertDefaultRouteResponseClass( diff --git a/packages/desktop/test/libs/utils.ts b/packages/desktop/test/libs/utils.ts index 9152f60bf..5af51ba00 100644 --- a/packages/desktop/test/libs/utils.ts +++ b/packages/desktop/test/libs/utils.ts @@ -141,6 +141,13 @@ class Utils { ); } + public async assertElementTextContains( + element: ChainablePromiseElement, + text: string + ): Promise { + expect(await element.getText()).toContain(text); + } + public async assertElementTextContain( element: ChainablePromiseElement, text: string