diff --git a/package.json b/package.json index 4e3a0db507..6c29816a5d 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "private": true, "scripts": { "start": "cd packages/selenium-ide && pnpm run start", + "start:test-site": "http-server -p 8080 ./tests/static", "build": "run-s build:ts build:ide", "build:electron": "pnpm run --stream --filter @seleniumhq/selenium-ide build:electron", "build:ide": "pnpm run --stream --filter @seleniumhq/selenium-ide build:webpack", diff --git a/packages/code-export-csharp-commons/src/command.ts b/packages/code-export-csharp-commons/src/command.ts index f83989b43d..609bb423dd 100644 --- a/packages/code-export-csharp-commons/src/command.ts +++ b/packages/code-export-csharp-commons/src/command.ts @@ -15,9 +15,15 @@ // specific language governing permissions and limitations // under the License. -import { codeExport as exporter, PrebuildEmitter } from 'side-code-export' -import { ExportFlexCommandShape, ProcessedCommandEmitter } from 'side-code-export/dist/code-export/emit' -import { ScriptShape } from 'side-code-export/src/code-export/preprocessor' +import { + codeExport as exporter, + PrebuildEmitter, + ExportFlexCommandShape, + ProcessedCommandEmitter, + EmitterContext, + ScriptShape, +} from 'side-code-export' +// eslint-disable-next-line node/no-unpublished-import import { CommandShape } from '@seleniumhq/side-model' import location from './location' import selection from './selection' @@ -130,10 +136,11 @@ function register(command: string, emitter: PrebuildEmitter) { exporter.register.emitter({ command, emitter, emitters }) } -function emit(command: CommandShape) { +function emit(command: CommandShape, context: EmitterContext) { return exporter.emit.command(command, emitters[command.command], { - variableLookup, + context, emitNewWindowHandling, + variableLookup, }) } @@ -302,7 +309,10 @@ function emitControlFlowIf(script: any) { }) } -function emitControlFlowForEach(collectionVarName: string, iteratorVarName: string) { +function emitControlFlowForEach( + collectionVarName: string, + iteratorVarName: string +) { return Promise.resolve({ commands: [ { @@ -500,12 +510,11 @@ async function emitMouseUp(locator: any) { return Promise.resolve({ commands }) } -function emitOpen(target: string) { +function emitOpen(target: string, _value: string, context: EmitterContext) { const url = /^(file|http|https):\/\//.test(target) - ? `"${target}"` - : // @ts-expect-error globals yuck - `"${global.baseUrl}${target}"` - return Promise.resolve(`driver.Navigate().GoToUrl(${url});`) + ? target + : `${context.project.url}${target}` + return Promise.resolve(`driver.Navigate().GoToUrl("${url}");`) } async function emitPause(time: any) { diff --git a/packages/code-export-csharp-xunit/src/command.ts b/packages/code-export-csharp-xunit/src/command.ts index 26551e71cd..62f5cac9cf 100644 --- a/packages/code-export-csharp-xunit/src/command.ts +++ b/packages/code-export-csharp-xunit/src/command.ts @@ -16,17 +16,19 @@ // under the License. import { Command, location } from '@seleniumhq/code-export-csharp-commons' -import { codeExport as exporter } from 'side-code-export' +import { EmitterContext, codeExport as exporter } from 'side-code-export' +// eslint-disable-next-line node/no-unpublished-import import { CommandShape } from '@seleniumhq/side-model' const emitters = { ...Command.emitters } exporter.register.preprocessors(emitters) -function emit(command: CommandShape) { +function emit(command: CommandShape, context: EmitterContext) { return exporter.emit.command(command, emitters[command.command], { - variableLookup: Command.variableLookup, + context, emitNewWindowHandling: Command.extras.emitNewWindowHandling, + variableLookup: Command.variableLookup, }) } @@ -154,7 +156,10 @@ async function emitVerifyNotEditable(locator: string) { emitters.assertNotSelectedValue = emitVerifyNotSelectedValue emitters.verifyNotSelectedValue = emitVerifyNotSelectedValue -async function emitVerifyNotSelectedValue(locator: string, expectedValue: string) { +async function emitVerifyNotSelectedValue( + locator: string, + expectedValue: string +) { const commands = [ { level: 0, statement: '{' }, { diff --git a/packages/code-export-java-junit/src/command.ts b/packages/code-export-java-junit/src/command.ts index 275568951d..7dcc39a6b9 100644 --- a/packages/code-export-java-junit/src/command.ts +++ b/packages/code-export-java-junit/src/command.ts @@ -15,8 +15,10 @@ // specific language governing permissions and limitations // under the License. +// eslint-disable-next-line node/no-unpublished-import import { CommandShape } from '@seleniumhq/side-model' import { + EmitterContext, codeExport as exporter, ExportFlexCommandShape, PrebuildEmitter, @@ -134,8 +136,9 @@ function register(command: string, emitter: PrebuildEmitter) { exporter.register.emitter({ command, emitter, emitters }) } -function emit(command: CommandShape) { +function emit(command: CommandShape, context: EmitterContext) { return exporter.emit.command(command, emitters[command.command], { + context, variableLookup, emitNewWindowHandling, }) @@ -496,11 +499,10 @@ async function emitMouseUp(locator: string) { return Promise.resolve({ commands }) } -function emitOpen(target: string) { +function emitOpen(target: string, _value: null, context: EmitterContext) { const url = /^(file|http|https):\/\//.test(target) ? `"${target}"` - : // @ts-expect-error globals yuck - `"${global.baseUrl}${target}"` + : `"${context.project.url}${target}"` return Promise.resolve(`driver.get(${url});`) } diff --git a/packages/code-export-javascript-mocha/src/command.ts b/packages/code-export-javascript-mocha/src/command.ts index 83eba1557c..1301e5c0c5 100644 --- a/packages/code-export-javascript-mocha/src/command.ts +++ b/packages/code-export-javascript-mocha/src/command.ts @@ -16,12 +16,14 @@ // under the License. import { + EmitterContext, codeExport as exporter, ExportFlexCommandShape, PrebuildEmitter, ProcessedCommandEmitter, ScriptShape, } from 'side-code-export' +// eslint-disable-next-line node/no-unpublished-import import { CommandShape } from '@seleniumhq/side-model' import location from './location' import selection from './selection' @@ -134,8 +136,9 @@ function register(command: string, emitter: PrebuildEmitter) { exporter.register.emitter({ command, emitter, emitters }) } -function emit(command: CommandShape) { +function emit(command: CommandShape, context: EmitterContext) { return exporter.emit.command(command, emitters[command.command], { + context, variableLookup, emitNewWindowHandling, }) @@ -182,7 +185,10 @@ function emitWaitForWindow() { }) } -async function emitNewWindowHandling(command: CommandShape, emittedCommand: ExportFlexCommandShape) { +async function emitNewWindowHandling( + command: CommandShape, + emittedCommand: ExportFlexCommandShape +) { return Promise.resolve( `vars["windowHandles"] = await driver.getAllWindowHandles()\n${await emittedCommand}\nvars["${ command.windowHandleName @@ -309,7 +315,10 @@ function emitControlFlowIf(script: ScriptShape) { }) } -function emitControlFlowForEach(collectionVarName: string, iteratorVarName: string) { +function emitControlFlowForEach( + collectionVarName: string, + iteratorVarName: string +) { return Promise.resolve({ commands: [ { @@ -519,12 +528,11 @@ async function emitMouseUp(locator: string) { return Promise.resolve({ commands }) } -function emitOpen(target: string) { +function emitOpen(target: string, _value: unknown, context: EmitterContext) { const url = /^(file|http|https):\/\//.test(target) - ? `"${target}"` - : // @ts-expect-error globals yuck - `"${global.baseUrl}${target}"` - return Promise.resolve(`await driver.get(${url})`) + ? target + : `${context.project.url}${target}` + return Promise.resolve(`await driver.get("${url}")`) } async function emitPause(time: string) { @@ -837,7 +845,10 @@ async function emitVerifyNotEditable(locator: string) { return Promise.resolve({ commands }) } -async function emitVerifyNotSelectedValue(locator: string, expectedValue: string) { +async function emitVerifyNotSelectedValue( + locator: string, + expectedValue: string +) { const commands = [ { level: 0, statement: `{` }, { @@ -848,7 +859,7 @@ async function emitVerifyNotSelectedValue(locator: string, expectedValue: string }, { level: 1, - statement: `assert.equal(value, "${exporter.emit.text(expectedValue)}")` + statement: `assert.equal(value, "${exporter.emit.text(expectedValue)}")`, }, { level: 0, statement: `}` }, ] @@ -864,7 +875,10 @@ async function emitVerifyNotText(locator: string, text: string) { locator )}).getText()`, }, - { level: 1, statement: `assert.notEqual(text, "${exporter.emit.text(text)}")` }, + { + level: 1, + statement: `assert.notEqual(text, "${exporter.emit.text(text)}")`, + }, { level: 0, statement: `}` }, ] return Promise.resolve({ commands }) diff --git a/packages/code-export-python-pytest/src/command.ts b/packages/code-export-python-pytest/src/command.ts index 9e47974131..a0e0ebbcdd 100644 --- a/packages/code-export-python-pytest/src/command.ts +++ b/packages/code-export-python-pytest/src/command.ts @@ -16,12 +16,14 @@ // under the License. import { + EmitterContext, codeExport as exporter, ExportFlexCommandShape, PrebuildEmitter, ProcessedCommandEmitter, ScriptShape, } from 'side-code-export' +// eslint-disable-next-line node/no-unpublished-import import { CommandShape } from '@seleniumhq/side-model' import location from './location' import selection from './selection' @@ -134,8 +136,9 @@ function register(command: string, emitter: PrebuildEmitter) { exporter.register.emitter({ command, emitter, emitters }) } -function emit(command: CommandShape) { +function emit(command: CommandShape, context: EmitterContext) { return exporter.emit.command(command, emitters[command.command], { + context, variableLookup, emitNewWindowHandling, }) @@ -499,12 +502,11 @@ async function emitMouseUp(locator: string) { return Promise.resolve({ commands }) } -function emitOpen(target: string) { +function emitOpen(target: string, _value: string, context: EmitterContext) { const url = /^(file|http|https):\/\//.test(target) - ? `"${target}"` - : // @ts-expect-error globals yuck - `"${global.baseUrl}${target}"` - return Promise.resolve(`self.driver.get(${url})`) + ? target + : `${context.project.url}${target}` + return Promise.resolve(`self.driver.get("${url}")`) } async function emitPause(time: number) { diff --git a/packages/code-export-ruby-rspec/src/command.ts b/packages/code-export-ruby-rspec/src/command.ts index 3bb8b87418..01ac79d2cf 100644 --- a/packages/code-export-ruby-rspec/src/command.ts +++ b/packages/code-export-ruby-rspec/src/command.ts @@ -16,11 +16,13 @@ // under the License. import { + EmitterContext, codeExport as exporter, ExportFlexCommandShape, ProcessedCommandEmitter, ScriptShape, } from 'side-code-export' +// eslint-disable-next-line node/no-unpublished-import import { CommandShape } from '@seleniumhq/side-model' import location from './location' import selection from './selection' @@ -133,8 +135,9 @@ function register(command: string, emitter: ProcessedCommandEmitter) { exporter.register.emitter({ command, emitter, emitters }) } -function emit(command: CommandShape) { +function emit(command: CommandShape, context: EmitterContext) { return exporter.emit.command(command, emitters[command.command], { + context, variableLookup, emitNewWindowHandling, }) @@ -479,11 +482,10 @@ async function emitMouseUp(locator: string) { return Promise.resolve({ commands }) } -function emitOpen(target: string) { +function emitOpen(target: string, _value: string, context: EmitterContext) { const url = /^(file|http|https):\/\//.test(target) ? `'${target}'` - : // @ts-expect-error globals yuck - `"${global.baseUrl}${target}"` + : `"${context.project.url}${target}"` return Promise.resolve(`@driver.get(${url})`) } @@ -767,7 +769,10 @@ async function emitVerifyNotEditable(locator: string) { return Promise.resolve({ commands }) } -async function emitVerifyNotSelectedValue(locator: string, expectedValue: string) { +async function emitVerifyNotSelectedValue( + locator: string, + expectedValue: string +) { const commands = [ { level: 0, diff --git a/packages/side-code-export/src/code-export/defaults.ts b/packages/side-code-export/src/code-export/defaults.ts index 63b8a1c59f..ed921243d6 100644 --- a/packages/side-code-export/src/code-export/defaults.ts +++ b/packages/side-code-export/src/code-export/defaults.ts @@ -14,8 +14,6 @@ export const languageFromOpts = ( beforeEachOptions, enableDescriptionAsComment, }) { - // @ts-expect-error globals yuck - global.baseUrl = baseUrl const testDeclaration = opts.generateTestDeclaration(test.name) const result = await emit.test(test, tests, { ...opts, diff --git a/packages/side-code-export/src/code-export/emit.ts b/packages/side-code-export/src/code-export/emit.ts index db7cc3567e..133a88a35e 100644 --- a/packages/side-code-export/src/code-export/emit.ts +++ b/packages/side-code-export/src/code-export/emit.ts @@ -80,6 +80,7 @@ function validateCommand(command: CommandShape) { export type ExportFlexCommandShape = ExportCommandShape | ExportCommandsShape export interface EmitCommandContext { + context: EmitterContext emitNewWindowHandling: ( command: CommandShape, result: ExportFlexCommandShape @@ -88,7 +89,11 @@ export interface EmitCommandContext { } export interface ProcessedCommandEmitter { - (target?: any, value?: any): Promise + ( + target: any | undefined, + value: any | undefined, + context: EmitterContext + ): Promise targetPreprocessor?: Preprocessor valuePreprocessor?: Preprocessor } @@ -99,8 +104,9 @@ export function baseEmitFactory( { variableLookup, emitNewWindowHandling }: EmitCommandContext ) { return async function emit( - target?: any, - value?: any + target: any | undefined, + value: any | undefined, + context: EmitterContext ): Promise { validateCommand(command) let _target = target @@ -111,7 +117,7 @@ export function baseEmitFactory( if (emitter.valuePreprocessor) { _value = await emitter.valuePreprocessor(value, variableLookup) } - const result = await emitter(_target, _value) + const result = await emitter(_target, _value, context) return emitNewWindowHandling(command, result) } } @@ -119,7 +125,7 @@ export function baseEmitFactory( export async function emitCommand( command: CommandShape, emitter: ProcessedCommandEmitter, - { variableLookup, emitNewWindowHandling }: EmitCommandContext + { context, emitNewWindowHandling, variableLookup }: EmitCommandContext ) { validateCommand(command) if (emitter) { @@ -136,7 +142,8 @@ export async function emitCommand( emitter.valuePreprocessor, variableLookup, { ignoreEscaping } - ) + ), + context ) if (command.opensWindow) { return await emitNewWindowHandling(command, result) @@ -195,14 +202,12 @@ export function emitSelection(location: string, emitters: SelectionEmitters) { } } -async function emitCommands( - commands: CommandShape[], - emitter: LanguageEmitterOpts['emitter'] -) { +async function emitCommands(commands: CommandShape[], context: EmitterContext) { + const { emitter } = context const emittedCommands = await Promise.all( commands.map((command) => { try { - return emitter.emit(command) + return emitter.emit(command, context) } catch (e) { throw new Error( `Error while emitting command ${command.command}|${command.target}|${ @@ -228,14 +233,8 @@ async function emitCommands( return result } -export interface EmitMethodContext - extends Pick< - EmitterContext, - | 'emitter' - | 'commandPrefixPadding' - | 'generateMethodDeclaration' - | 'terminatingKeyword' - > { +export interface EmitMethodContext { + context: EmitterContext level: number render: any overrideCommandEmitting?: boolean @@ -253,16 +252,13 @@ export interface NonOverrideMethodShape { async function emitMethod( method: MethodShape, - { + { context, level, render, overrideCommandEmitting = false }: EmitMethodContext +) { + const { commandPrefixPadding, generateMethodDeclaration, - level, terminatingKeyword, - emitter, - render, - overrideCommandEmitting = false, - }: EmitMethodContext -) { + } = context const methodDeclaration = generateMethodDeclaration(method.name) let _methodDeclaration = methodDeclaration let _terminatingKeyword = terminatingKeyword @@ -278,7 +274,7 @@ async function emitMethod( }) } else { result = render( - await emitCommands(method.commands as CommandShape[], emitter), + await emitCommands(method.commands as CommandShape[], context), { newLineCount: 0, startingLevel: level, @@ -346,9 +342,11 @@ export interface EmittedTest { async function emitTest( test: TestShape, tests: TestShape[], - { - testLevel, - commandLevel, + context: EmitterContext +): Promise { + const { + testLevel = 1, + commandLevel = 2, testDeclaration, terminatingKeyword, commandPrefixPadding, @@ -359,12 +357,8 @@ async function emitTest( enableOriginTracing, enableDescriptionAsComment, project, - }: EmitterContext -): Promise { - // preamble + } = context let result: Partial = {} - testLevel = testLevel || 1 - commandLevel = commandLevel || 2 const methods = findReusedTestMethods(test, tests) const render = (...args: PartialRenderParameters) => doRender(commandPrefixPadding, ...args) @@ -378,12 +372,9 @@ async function emitTest( ) { const method = await emitter.extras[emitter_name]() const result = await emitMethod(method, { - emitter, - commandPrefixPadding, - generateMethodDeclaration: method.generateMethodDeclaration, + context, level: testLevel, render, - terminatingKeyword, overrideCommandEmitting: true, }) await registerMethod(method.name, result, { @@ -397,12 +388,9 @@ async function emitTest( // handle reused test methods (e.g., commands that use the `run` command) for (const method of methods) { const result = await emitMethod(method, { - emitter, - commandPrefixPadding, - generateMethodDeclaration, + context, level: testLevel, render, - terminatingKeyword, }) await registerMethod(method.name, result, { generateMethodDeclaration, @@ -430,7 +418,7 @@ async function emitTest( ) as string result.commands = render( { - commands: await emitCommands(test.commands, emitter).catch((error) => { + commands: await emitCommands(test.commands, context).catch((error) => { // prefix test name on error throw new Error(`Test '${test.name}' has a problem: ${error.message}`) }), diff --git a/packages/side-code-export/src/types.ts b/packages/side-code-export/src/types.ts index c421e16385..1440c1da34 100644 --- a/packages/side-code-export/src/types.ts +++ b/packages/side-code-export/src/types.ts @@ -4,7 +4,11 @@ import { SuiteShape, TestShape, } from '@seleniumhq/side-model' -import { ExportFlexCommandShape, VariableLookup } from './code-export' +import { + EmitterContext, + ExportFlexCommandShape, + VariableLookup, +} from './code-export' import Hook, { LanguageHooks } from './code-export/hook' @@ -68,11 +72,13 @@ export interface EmitOptions { export type PrebuildEmitter = ( target: string, - value: string + value: string, + context: EmitterContext ) => Promise export type LanguageExportEmitterEmit = ( - command: CommandShape + command: CommandShape, + context: EmitterContext ) => Promise | ExportFlexCommandShape export type LanguageExportEmitter = { diff --git a/tests/examples/plugin.side b/tests/examples/plugin.side index d1ce08a040..1272c56d47 100644 --- a/tests/examples/plugin.side +++ b/tests/examples/plugin.side @@ -2,7 +2,7 @@ "id": "8e43685a-112a-455b-a6b2-748a19071100", "version": "2.0", "name": "Plugin", - "url": "https://www.google.com", + "url": "http://localhost:8080/", "tests": [ { "id": "58cc1d1f-7920-47ee-a0eb-336a3ebdf069", @@ -12,7 +12,7 @@ "id": "161321ce-3c63-4283-989e-d20117a97eb8", "comment": "", "command": "open", - "target": "/webhp", + "target": "/mouse/down.html", "targets": [], "value": "" }, @@ -24,7 +24,7 @@ }, { "command": "customClick", - "target": "css=.lnXdpd", + "target": "id=a", "value": "", "insertBeforeLastCommand": false, "frameLocation": "root", @@ -40,88 +40,13 @@ ] ] }, - { - "id": "d6702ef2-8787-4252-9861-5aa28d65ab4e", - "comment": "", - "command": "type", - "target": "name=q", - "targets": [ - [ - "id=lst-ib", - "id" - ], - [ - "name=q", - "name" - ], - [ - "css=#lst-ib", - "css" - ], - [ - "xpath=//input[@id='lst-ib']", - "xpath:attributes" - ], - [ - "xpath=//div[@id='gs_lc0']/input", - "xpath:idRelative" - ], - [ - "xpath=//div[2]/div/input", - "xpath:position" - ] - ], - "value": "selenium ide" - }, - { - "id": "1b4d1d17-2e2e-4557-9cfa-260189c4b6a7", - "comment": "", - "command": "sendKeys", - "target": "name=q", - "targets": [ - [ - "id=lst-ib", - "id" - ], - [ - "name=q", - "name" - ], - [ - "css=#lst-ib", - "css" - ], - [ - "xpath=//input[@id='lst-ib']", - "xpath:attributes" - ], - [ - "xpath=//div[@id='gs_lc0']/input", - "xpath:idRelative" - ], - [ - "xpath=//div[2]/div/input", - "xpath:position" - ] - ], - "value": "${KEY_ENTER}", - "isBreakpoint": false - }, - { - "id": "5628fe89-f130-40cb-b52e-244a41cd6516", - "comment": "", - "command": "click", - "target": "css=.g a", - "targets": [], - "value": "" - }, { "id": "fa9bae47-4a7e-4e92-9e02-e6863c13aaf1", "comment": "", - "command": "assertTitle", - "target": "Selenium IDE ยท Open source record and playback test automation for the web", + "command": "waitForText", + "target": "id=a", "targets": [], - "value": "" + "value": "down" } ] } @@ -142,6 +67,6 @@ "https://www.google.com/" ], "plugins": [ - "../../packages/side-example-suite/dist/plugins/custom-click/index.js" + "../../packages/side-example-suite/dist/plugins/custom-click" ] } \ No newline at end of file