SeleniumHQ / selenium-ide

Open Source record and playback test automation for the web.
https://selenium.dev/selenium-ide/
Apache License 2.0
2.8k stars 762 forks source link

How to wrap emitted by side-code-export commands in a try catch block? #1660

Closed matewilk closed 1 year ago

matewilk commented 1 year ago

💬 Questions and Help

Please note that this issue tracker is not a help form and this issue will be closed.

So I have written an exporter which utilises side-code-export which generates a.js` file which looks something like this:

// Generated by Selenium IDE
const assert = require('assert');

// suite declaration New Relic Suite
const By = $selenium.By;
const until = $selenium.until;
const vars = new Map();

// beforeEach
  // test declaration newrelic
await $webDriver.get("https://newrelic.com/")
await $webDriver.manage().window().setRect({ width: 1680, height: 940 })
await $webDriver.wait(until.elementLocated(By.css(".hidden > .js-header-search-trigger svg"))).click()
await $webDriver.wait(until.elementLocated(By.id("header-search"))).sendKeys("synthetics")
await $webDriver.wait(until.elementLocated(By.css(".js-search-form-submit svg"))).click()
await $webDriver.wait(until.elementLocated(By.css(".st-app-result:nth-child(1) .st-app-result__title"))).click()
  // terminating keyword
// terminating keyword

I find it difficult to wrap my head around on how do I wrap the emitted commands (in between // test declartation newrelic and // terminating keyword with a try catch block?

I've gone through the code, tested out a few things, but I don't see a way to do it, in theory I could use either beforeEach hook or generateTestDeclaration for the try { part but then I'm not finding anything that would allow me to close it with } catch (e) { ... }. The // terminating keyword in the example above is used for both test and suite termination, and it always pops up twice, so it would create invalid (and repeated) syntax.

Any ideas on how to make it work would be much appreciated.

By the way, my index.ts looks like this:

import {
  codeExport as exporter,
  generateHooks,
  LanguageEmitterOpts,
  languageFromOpts,
} from "side-code-export";
import emitter from "./command";
import location from "./location";
import hooks from "./hook";
import { logger } from "./utils";

export const displayName = "JavaScript New Relic Synthetics";

export const opts: LanguageEmitterOpts = {
  emitter: emitter,
  displayName,
  name: "javascript-newrelic-synthetics",
  hooks: generateHooks(hooks),
  fileExtension: ".js",
  commandPrefixPadding: "  ",
  terminatingKeyword: "// terminating keyword",
  commentPrefix: "//",
  generateMethodDeclaration: function generateMethodDeclaration(name: string) {
    return `// method declaration ${name}`;
  },
  // Create generators for dynamic string creation of primary entities (e.g., filename, methods, test, and suite)
  generateTestDeclaration: function generateTestDeclaration(name: string) {
    return `// test declaration ${name}`;
  },
  generateSuiteDeclaration: function generateSuiteDeclaration(name) {
    return `// suite declaration ${name}
    ${logger}`;
  },
  generateFilename: function generateFilename(name) {
    return `${exporter.parsers.uncapitalize(
      exporter.parsers.sanitizeName(name)
    )}${opts.fileExtension}`;
  },
};

export default languageFromOpts(opts, location.emit);

and hooks hook.ts:

import { HookFunctionInputs } from "side-code-export";

const emitters = {
  afterAll,
  afterEach,
  beforeAll,
  beforeEach,
  declareDependencies,
  declareMethods: empty,
  declareVariables,
  inEachBegin: empty,
  inEachEnd: empty,
};

export default emitters;

function afterAll() {
  const params: HookFunctionInputs = {
    startingSyntax: {
      commands: [],
    },
    endingSyntax: {
      commands: [],
    },
    registrationLevel: 1,
  };
  return params;
}

function afterEach() {
  const params: HookFunctionInputs = {
    startingSyntax: {
      commands: [],
    },
    endingSyntax: {
      commands: [],
    },
  };
  return params;
}

function beforeAll() {
  const params: HookFunctionInputs = {
    startingSyntax: {
      commands: [],
    },
    endingSyntax: {
      commands: [],
    },
    registrationLevel: 1,
  };
  return params;
}

function beforeEach() {
  const params: HookFunctionInputs = {
    startingSyntax: {
      commands: [
        {
          level: 0,
          statement: `// beforeEach`,
        },
      ],
    },
    endingSyntax: {
      commands: [],
    },
  };
  return params;
}

function declareDependencies() {
  const params: HookFunctionInputs = {
    startingSyntax: {
      commands: [
        {
          level: 0,
          statement: `const assert = require('assert');`,
        },
      ],
    },
  };
  return params;
}

function declareVariables() {
  const params: HookFunctionInputs = {
    startingSyntax: {
      commands: [
        {
          level: 0,
          statement: `const By = $selenium.By;`,
        },
        {
          level: 0,
          statement: `const until = $selenium.until;`,
        },
        {
          level: 0,
          statement: `const vars = new Map();`,
        },
      ],
    },
  };
  return params;
}

function empty() {
  return {};
}

For some reason I also can't hook up to afterAll, afterEach and beforeAll for some reason, inEachBegin and inEachEnd don't seem to work either. (as a side note really, not that this would help with wrapping all the commands with try catch)

Also, this is how I export my commands to the index.ts file from command.ts:


// bunch of commands here .....

function emit(command: CommandShape, context: EmitterContext) {
  return exporter.emit.command(command, emitters[command.command], {
    context,
    variableLookup,
    emitNewWindowHandling,
  });
}

const skip = async () => Promise.resolve("");

export const emitters: Record<string, ProcessedCommandEmitter> = {
  addSelection: emitSelect,
  assert: emitAssert,
  assertAlert: emitAssertAlert,
  check: emitCheck,
  chooseCancelOnNextConfirmation: skip,
  chooseCancelOnNextPrompt: skip,
  chooseOkOnNextConfirmation: skip,
  open: emitOpen,
  click: emitClick,
  clickAt: emitClick,
  close: emitClose,
  debugger: skip,
  doubleClick: emitDoubleClick,
  doubleClickAt: emitDoubleClick,
  dragAndDropToObject: emitDragAndDrop,
  echo: emitEcho,
  editContent: emitEditContent,
  else: skip,
  elseIf: skip,
  end: skip,
  executeScript: emitExecuteScript,
  executeAsyncScript: emitExecuteAsyncScript,
  removeSelection: emitSelect,
  select: emitSelect,
  selectFrame: emitSelectFrame,
  selectWindow: emitSelectWindow,
  setWindowSize: emitSetWindowSize,
  storeWindowHandle: emitStoreWindowHandle,
  type: emitType,
  webdriverAnswerOnVisiblePrompt: emitAnswerOnNextPrompt,
  webdriverChooseCancelOnVisibleConfirmation:
    emitChooseCancelOnNextConfirmation,
  webdriverChooseOkOnVisibleConfirmation: emitChooseOkOnNextConfirmation,
};

exporter.register.preprocessors(emitters);

function register(command: string, emitter: PrebuildEmitter) {
  exporter.register.emitter({ command, emitter, emitters });
}

export default {
  emit,
  emitters,
  extras: { emitNewWindowHandling, emitWaitForWindow },
  register,
};

For questions or help please see:

matewilk commented 1 year ago

Hey @toddtarsi, if you could also be so kind and advise on this one I'd be very grateful!

matewilk commented 1 year ago

I also tried overriding suite / emitSuite method from defaults.ts but unfortunately to no avail.

So I'm a little stack here ...

toddtarsi commented 1 year ago

@matewilk - Right, I see whats going on! I remember for a while I was trying to write try catch as a command node. That stuff is tricky. I'll take a look later tonight. I think it's doable, albeit messy and kinda kludgy (also I am usually wrong)

matewilk commented 1 year ago

Much appreciated, it's important from the perspective of what I'm building so I'd be grateful if you could look into it.

toddtarsi commented 1 year ago

@matewilk - Would you review this PR?

https://github.com/SeleniumHQ/selenium-ide/pull/1662

I think this should make that capability pretty straightforward.

toddtarsi commented 1 year ago

@matewilk - If this looks alright to you now, please close this issue. Otherwise, feel free to let me know if you see anything wrong when you get there (no pressure)

matewilk commented 1 year ago

Hey, thanks for the reminder, closing!

github-actions[bot] commented 1 year ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.