Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/viesti/cypress-clojurescript-preprocessor

ClojureScript preprocessor for Cypress
https://github.com/viesti/cypress-clojurescript-preprocessor

Last synced: about 2 months ago
JSON representation

ClojureScript preprocessor for Cypress

Awesome Lists containing this project

README

        

# cypress-clojurescript-preprocessor

A [Cypress preprocessor](https://docs.cypress.io/api/plugins/preprocessors-api.html) for [ClojureScript](https://clojurescript.org/), which uses [Shadow CLJS](https://github.com/thheller/shadow-cljs) for processing Cypress tests written in ClojureScript.

The plugin works by inspecting the Cypress `integrationFolder` (by default, `cypress/integration`) for ClojureScript files (`*.cljs`, e.g. `cypress/integration/my_app/app_test.cljs` -> `(ns my-app.app-test)`) and generates a `shadow-cljs.edn` configuration file with a build for each test file. This configuration is then used to compile the tests into Javascript before submitting to the browser.

The shadow-cljs server is kept running while the Cypress runner is active. When a test is run, the test file is watched for changes and a recompile is done if the test file is changed and Cypress is notified to rerun the test. The first compile is a bit slow, but subsequent compiles are fast (the plugin uses shadow-cljs `compile` command, which defaults to [`optimizations: none`](https://shadow-cljs.github.io/docs/UsersGuide.html#Optimization)).

## Rationale: Should you use this?

I think a small word of warning should be in order if you intend to use this plugin :) I had a great time hacking this together over a period of time, but maxed out at getting a REPL flow working, which I think makes the current implementation quite complex, since Cypress isn't quite REPL friendly (check out [the issue on REPL support](https://github.com/cypress-io/cypress/issues/19427)). Also, I haven't updated the plugin to work with latest Cypress versions, which has changed the preprocessor API, so I don't think the current latest versio of this preprocessor works with current latest Cypress.

There's good reading on rationale in https://github.com/viesti/cypress-clojurescript-preprocessor/issues/12 and possible future directions in https://github.com/viesti/cypress-clojurescript-preprocessor/issues/16, so I suggesst checking them out. If you are still interested, continue reading :)

## Example usage

1. Create a project

```sh
$ mkdir cypress-cljs-sample
$ cd cypress-cljs-sample
$ echo {} > package.json
```

2. Install [Cypress](https://docs.cypress.io/guides/getting-started/installing-cypress.html#Installing)

```sh
npm install cypress --save-dev
```

3. Install cypress-clojurescript-preprocessor

```sh
npm install cypress-clojurescript-preprocessor
```

4. Configure ClojureScript preprocessor

```sh
$ mkdir -p cypress/plugins
$ cat << EOF > cypress/plugins/index.js
const makeCljsPreprocessor = require('cypress-clojurescript-preprocessor');
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
on('file:preprocessor', makeCljsPreprocessor(config));
};
EOF
```

This will delegate files other than `*.cljs` to the [default Browserify preprocessor](https://docs.cypress.io/api/plugins/preprocessors-api.html#Defaults). If you need to run other preprocessors, then combine them with for example:

```js
module.exports = (on, config) => {
const browserifyPreprocessor = makeBrowserifyPreprocessor();
const cljsPreprocessor = makeCljsPreprocessor(config);
// Use the default Browserify preprocessor for files other than *.cljs
on('file:preprocessor', (file) => file.filePath.endsWith('.cljs') ? cljsPreprocessor(file) : browserifyPreprocessor(file));
};
```
5. Write test in ClojureScript

```sh
$ mkdir -p cypress/integration/examples
$ cat << EOF > cypress/integration/examples/window.cljs
(ns examples.window
(:require-macros [latte.core :refer [describe beforeEach it]]))

(def cy js/cy)

(describe "Window"
(beforeEach []
(.visit cy "https://example.cypress.io/commands/window"))
(it "cy.window() - get the global window object" []
(.should (.window cy) "have.property" "top")))
EOF
```
6. Run test

```sh
./node_modules/.bin/cypress open
```

## REPL in Cypress

You can active REPL into a test in the browser that Cypress is controlling.

1. Lookup NREPL port used by the shadow-cljs server

```
$ cat .preprocessor-cljs/.shadow-cljs/nrepl.port
50796
```

2. Connect to the NREPL server and list builds

```
shadow.user> (shadow/get-build-ids)
(:npm :local :window_foo :window)
```

3. Open a test in the Cypress runner

4. Start shadow-cljs watch on the test that is open in the Cypress runner

```
shadow.user> (shadow/watch :window)
[:window] Configuring build.
[:window] Compiling ...
[:window] Build completed. (119 files, 0 compiled, 0 warnings, 1.22s)
:watching
```

The build-id is a keyword of the test file name

5. Start a ClojureScript repl

```
shadow.user> (shadow/repl :window)
To quit, type: :cljs/quit
[:selected :window]
cljs.user> (js/alert "plop") ;; To try out that it works
```

Now you can use the slightly undocumented `now` command to execute Cypress command immediately, for example, to select an option:

```
cljs.user> (-> (.now js/cy "get" "#some-opt")
(.then (fn [el]
(.now js/cy "select" el "two"))))
#object[Promise [object Promise]]
```

6. When done, exit the repl and stop shadow-cljs watch for the repl build

```
cljs.user> :cljs/quit
Exited CLJS session. You are now in CLJ again.
:cljs/quit
shadow.user>
(shadow/stop-worker :window)
Worker shutdown.
:stopped
```

Some references to the `now` command:

* https://github.com/cypress-io/cypress/issues/6080#issuecomment-570481923
* https://docs.cypress.io/guides/guides/debugging#Run-Cypress-command-outside-the-test
* https://github.com/cypress-io/cypress/issues/8195
* https://github.com/cypress-io/cypress/issues/3636#issuecomment-511315778

## Configuration

### Shadow CLJS configuration override

The Shadow CLJS configuration used by the preprocessor may be overridden via a `shadow-cljs-override.edn` file, which is merged on top of the default configuration with [meta-merge](https://github.com/weavejester/meta-merge). By default, `[mocha-latte "0.1.2"]` and `[chai-latte "0.2.0"]` are included in the shadow-cljs.edn configuration used by the preprocessor.

### Shared namespaces

The working directory of the shadow-cljs compiler that is driven by the preprocessor is the `cypress` folder. Because of this, in order to include shared code that is in a folder at the same level as the `cypress` directory, use a relative (or absolute path) e.g.

```
$ tree -I "node_modules" -P common.cljs\|window.cljs
.
├── cypress
│   ├── fixtures
│   ├── integration
│   │   └── examples
│   │   └── window.cljs
│   ├── plugins
│   └── support
└── src ;; shared code directory
└── net
└── tiuhti
└── common.cljs
$ cat shadow-cljs-override.edn
{:source-paths ["../src"]} ;; path relative to `cypress` directory
$ head -3 cypress/integration/examples/window.cljs
(ns examples.window
(:require-macros [latte.core :refer [describe beforeEach it]])
(:require [net.tiuhti.common :as common])) ;; require namespace in the `src` directory
```

Dependencies used by the shared code would have to be specified in the `:dependencies` key in `shadow-cljs-override.edn` file.

## Development

Here's some notes on how to develop this library.

### Shadow-cljs repl

The preprocessor is written in ClojureScript and compiled to a npm library with the help of shadow-cljs. To develop the library in a ClojureScript repl:

1. Start node repl via `shadow-cljs node-repl`
2. Connect to the `nrepl` server for example from Emacs with cider: `cider-connect`
3. In `nrepl` at the `shadow.user>` prompt, switch to the node repl via: ` (shadow/repl :node-repl)`

### Use locally compiled version

To use a local version of the preprocessor, point the client project to the local clone of the preprocessor:

```0% cat package.json
{
"devDependencies": {
"cypress": "^7.6.0",
"shadow-cljs": "^2.14.5" ;; <-- Also, need to have shadow-cljs installed in the client project
},
"dependencies": {
"cypress-clojurescript-preprocessor": "../cypress-clojurescript-preprocessor" ;; <-- Directory of the local copy
}
}
```

Note the the client project also needs shadow-cljs to be installed (apparently :)).

Then, compile your changes via

```
$ npm run prepare
```

And (re)start Cypress in the client project:

```
$ ./node_modules/.bin/cypress open
```

## Changelog

* 0.2.0
* Support REPL in an open test, allows to use the "now" command
* Finally fix zombie processes after exit, by using [signal-exit](https://azimi.me/2014/12/31/kill-child_process-node-js.html), thanks to [this blog post](https://azimi.me/2014/12/31/kill-child_process-node-js.html)
* Detech shadow-cljs startup by polling port/pid files and remove race condition around subprocess stdout/stderr
* 0.1.7
* Bump shadow-cljs to 2.14.5
* Call process/exit to not leave zombies when Cypress exits
* When adding a new test while the preprocessor is running
* Use same optimization level as for initial build config
* Prevent duplicate merge of `shadow-cljs-override.edn`
* 0.1.6 Support namespaces with multiple segments (thanks @martinklepsch !), also files with underscore. Bump chokidar to 3.5.2
* 0.1.5 Merge override config ontop final config, allows to add additional :source-paths
* 0.1.4 Bundle browserify preprocessor
* 0.1.3 Add shadow-cljs-override.edn
* 0.1.2 Bundle Bundle mocha-latte and chai-latte
* 0.1.0 Initial release