Skip to main content

Understanding Plugin Lifecycle

Cycle controls step plugins through a series of lifecycle phases during test execution. Cycle will run one local instance of each step plugin for the duration of one test suite launch, and it will call the plugin's API endpoints during lifecycle phases.

The plugin SDK provides all the support for a plugin's lifecycle and API endpoints. If you develop your plugin with a plugin SDK, then you do not need to implement anything extra beyond the step definitions and the fixtures. The content on this page is primarily informational to help you better understand how Cycle interacts with plugins.

Lifecycle phases

Reading the steps

The CycleScript language server must read all plugins' step specs whenever it starts up so that it can properly parse feature files and perform syntax analysis. This will happen:

  1. when Cycle executes a feature file
  2. in VS Code, when the Cycle Testing extension starts
  3. in the Cycle Desktop IDE, upon the app's startup

Starting the plugin

When Cycle executes a feature file, it starts each step plugin as a child process using the start command from the plugin's settings file. Since plugins are HTTP services running on localhost, Cycle must also assign unique ports when starting each plugin. (Port management is necessary for managing plugin dependencies.)

Cycle will start one instance of each plugin for a test run. That means all scenarios in the suite will operate with one plugin instance, which means that the plugin will need to manage state for multiple, potentially concurrent scenarios. The plugin SDK provides protections for managing test data safely so that a scenario cannot access test data from another scenario.

Waiting for the health check

After starting a plugin, Cycle will call the plugin's health check endpoint (GET /service/status) every 3 seconds for up to 1 minute to wait for the plugin to be ready. Once the health check is successful, Cycle will proceed with test execution. If the health check repeatedly fails, then Cycle will abort test execution.

Setting dependencies on other plugins

A plugin might declare dependencies on other plugins in its settings file in order to call steps that those other plugins provide. Before starting any tests (like a "beforeAll" hook), Cycle will call the plugin's POST /service/dependencies endpoint to pass the base URLs for all the plugin's dependencies. (This is why Cycle must control port assignment for plugins.)

Setting the Cycle settings

Cycle projects have standard project settings (.cycproj file) and user settings (.cycuser) that control how tests are run. These settings have suite-wide scope, meaning that they are accessible to all tests during test execution. They are also immutable during a test run. For simplicity and convenience, Cycle passes all settings to each plugin at the start of a test run. This is performed via a one-time API call to POST /suite/start before any tests are run (like a "beforeAll" hook). The plugin then stores the settings and injects them into step definitions through context objects so that the steps can access any settings they need.

Marking the start of a suite

When a suite starts, Cycle calls each plugin's POST /suite/start endpoint. Internally, the plugin SDK creates a suite context object for storing all objects required for the suite, which it injects into step definitions so that steps can access the context. The plugin SDK also calls fixtures for suite-level setup operations.

Marking the start of a scenario

When a scenario starts, Cycle calls each plugin's POST /scenario/{scenarioId}/start endpoint. The plugin SDK creates a scenario context object for storing all objects required for the scenario with the given test ID. For example, web steps need a shared WebDriver object, so the WebDriver plugin will store the WebDriver object for a scenario in the scenario's context object. The plugin SDK injects the scenario context along with the suite context into step definitions so that steps can access and modify the context. The plugin SDK also calls fixtures for scenario-level setup operations.

Running a step

During scenario execution, when Cycle runs a step that belongs to a plugin, it calls the POST /scenario/{scenarioId}/step endpoint and passes the step ID, parameters, and input variables through the request body. The plugin SDK receives the request, fetches the appropriate context objects, and calls the appropriate step definition to run the desired step.

Marking the end of a scenario

When a scenario ends, Cycle calls each plugin's POST /scenario/{scenarioId}/end endpoint. The plugin SDK calls fixtures for scenario-level cleanup operations. For example, the WebDriver plugin may need to safely quit any open browsers. Then, after performing any custom cleanup logic, the plugin SDK removes the given scenario's context object.

Marking the end of a suite

When a suite ends, Cycle calls each plugin's POST /suite/end endpoint. The plugin SDK calls fixtures for suite-level cleanup operations.

Shutting down the plugin

When a test suite run is complete, Cycle must safely shut down all plugin processes. It calls the POST /service/shutdown endpoint for each plugin.

API endpoints

The plugin SDK provides the following endpoints for integration with Cycle:

  • GET /service/status: get a health check
  • POST /service/dependencies: set dependencies upon other step plugins
  • POST /suite/start: mark the start of a suite and pass Cycle settings
  • POST /scenario/{scenarioId}/start: mark the start of a scenario
  • POST /scenario/{scenarioId}/step: run a step
  • POST /scenario/{scenarioId}/end: mark the end of a scenario
  • POST /suite/end: mark the end of a suite
  • POST /service/shutdown: shutdown the service