{"id":15403779,"url":"https://github.com/palkan/wsdirector","last_synced_at":"2025-09-23T15:32:46.685Z","repository":{"id":43026234,"uuid":"88237133","full_name":"palkan/wsdirector","owner":"palkan","description":"All the world's a server, and all the men and women merely clients","archived":false,"fork":false,"pushed_at":"2024-10-04T23:34:22.000Z","size":177,"stargazers_count":104,"open_issues_count":0,"forks_count":17,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-02T03:13:52.387Z","etag":null,"topics":["actioncable","cli","testing","websockets"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/palkan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-04-14T05:48:14.000Z","updated_at":"2024-10-08T12:10:30.000Z","dependencies_parsed_at":"2023-02-09T13:15:39.137Z","dependency_job_id":"999b8f2c-b969-4d7e-b85d-906743898b61","html_url":"https://github.com/palkan/wsdirector","commit_stats":{"total_commits":94,"total_committers":6,"mean_commits":"15.666666666666666","dds":"0.18085106382978722","last_synced_commit":"13d7f1e8dd5dc8c968f1b511b199976916c9eed0"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palkan%2Fwsdirector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palkan%2Fwsdirector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palkan%2Fwsdirector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palkan%2Fwsdirector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/palkan","download_url":"https://codeload.github.com/palkan/wsdirector/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247974732,"owners_count":21026742,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actioncable","cli","testing","websockets"],"created_at":"2024-10-01T16:10:04.037Z","updated_at":"2025-09-23T15:32:41.661Z","avatar_url":"https://github.com/palkan.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](http://cultofmartians.com/tasks/websocket-director.html)\n[![Gem Version](https://badge.fury.io/rb/wsdirector-cli.svg)](https://rubygems.org/gems/wsdirector-cli)\n[![Build](https://github.com/palkan/wsdirector/workflows/Build/badge.svg)](https://github.com/palkan/wsdirector/actions)\n\n# WebSocket Director\n\nCommand line tool for testing WebSocket servers using scenarios.\n\nSuitable for testing any websocket server implementation, like [Action Cable](https://github.com/rails/rails/tree/master/actioncable), [AnyCable](https://anycable.io), [Phoenix Channels](https://hexdocs.pm/phoenix/channels.html), [GraphQL WS](https://github.com/enisdenjo/graphql-ws) and so on.\n\n\u003e 📖 Read also [WebSocket Director: scenario-based integration tests for realtime apps](https://evilmartians.com/chronicles/websocket-director-scenario-based-integration-tests-for-real-time-apps)\n\n## Installation\n\nInstall CLI:\n\n```sh\ngem install wsdirector-cli\n```\n\nOr use WebSockets Director as a library (see below for intructions):\n\n```ruby\n# Gemfile\ngem \"wsdirector-core\", \"~\u003e 1.0\"\n```\n\n## Usage\n\nCreate YAML file with simple testing script:\n\n```yml\n# script.yml\n- receive: \"Welcome\" # expect to receive message\n- send:\n    data: \"send message\" # send message, all messages in data will be parse to json\n- receive:\n    data: \"receive message\" # expect to receive json message\n```\n\nand run it with this command:\n\n```bash\nwsdirector -f script.yml -u ws://websocket.server:9876/ws\n\n#=\u003e 1 clients, 0 failures\n```\n\nYou can also use positional arguments:\n\n```sh\nwsdirector script.yml ws://websocket.server:9876/ws\n```\n\nYou can create more complex scenarios with multiple client groups:\n\n```yml\n# script.yml\n- client: # first clients group\n    name: \"publisher\" # optional group name\n    multiplier: \":scale\" # :scale take number from -s param, and run :scale number of clients in this group\n    actions:\n      - receive:\n          data: \"Welcome\"\n      - wait_all # makes all clients in all groups wait untill every client get this point (global barrier)\n      - send:\n          data: \"test message\"\n- client:\n    name: \"listeners\"\n    multiplier: \":scale * 2\"\n    actions:\n      - receive:\n          data: \"Welcome\"\n      - wait_all\n      - receive:\n          multiplier: \":scale\" # you can use multiplier with any action\n          data: \"test message\"\n```\n\nRun with scale factor:\n\n```bash\nwsdirector -f script.yml -u ws://websocket.server:9876 -s 10\n\n#=\u003e Group publisher: 10 clients, 0 failures\n#=\u003e Group listeners: 20 clients, 0 failures\n```\n\nThe simpliest scenario is just checking that socket is succesfully connected:\n\n```yml\n- client:\n    name: connection check\n    # no actions\n```\n\nRun with loop option:\n\n```yml\n  # script.yml\n  - client:\n      name: \"listeners\"\n      loop:\n        multiplier: \":scale\" # :scale take number from -s param, and run :scale number of clients in this group\n        actions:\n          - receive:\n              data:\n                type: \"welcome\"\n          - send:\n              data:\n                command: \"subscribe\"\n                identifier: \"{\\\"channel\\\":\\\"Channel\\\"}\"\n          - receive:\n              data:\n                identifier: \"{\\\"channel\\\":\\\"Channel\\\"}\"\n                type: \"confirm_subscription\"\n          - wait_all\n          - receive:\n              multiplier: \":scale + 1\"\n```\n\nBy default, `receive` action expects the exact `data` match. In some cases, it's useful to only match the specified keys (inclusion). For that, you can use `data\u003e` field instead:\n\n```yml\n- client:\n  actions:\n    - receive:\n        data:\n          type: \"welcome\"\n    - send:\n        data:\n          command: \"subscribe\"\n          identifier: \"{\\\"channel\\\":\\\"Channel\\\"}\"\n    - receive:\n        data\u003e:\n          type: \"confirm_subscription\"\n```\n\nAlso you can pass a JSON file with some testing scripts:\n\n```bash\nwsdirector -f scenario.json -u ws://websocket.server:9876\n```\n\nor pass a JSON scenario directly to the CLI without creating a file:\n\n```bash\nwsdirector -i '[{\"receive\": {\"data\":\"welcome\"}},{\"send\":{\"data\":\"send message\"}},{\"receive\":{\"data\":\"receive message\"}}]' -u ws://websocket.server:9876\n```\n\nType `wsdirector --help` to check all commands.\n\n### Receive order\n\nBy default, the `receive` action scans through all available or newly added message to find a matching one.\nIf you want to check the order of incoming messages, add the `ordered: true` option to the `receive` action.\n\n### Connection configuration\n\nYou can specify client's headers, cookies or query string params via the `connection_options` directive:\n\n```yml\n- client:\n    connection_options:\n      headers:\n        \"X-API-KEY\": \"secret\"\n      query:\n        token: \"123\"\n      cookies:\n        session_id: \"2022\"\n```\n\n**NOTE**: Query string params could also be passed as a part of the URL. Specifying them in the scenario allows you to provide values via the interpolation.\n\n### Using as a library\n\nYou can integrate WS Director into your library or application by using its APIs:\n\n```ruby\n# Could be a file path or JSON-encoded string as well\nscenario = [\n  {\n    send: {\n      data: \"ping\"\n    }\n  },\n  {\n    receive: {\n      data: \"pong\"\n    }\n  }\n]\n\nresult = WSDirector.run(scenario, url: \"ws://my.ws.server:4949/live\")\nresult.success? #=\u003e true of false\nresult.groups #=\u003e result data for each client group\n```\n\nIf you're using YAML-based scenarios, you can also pass local variables to be used with ERB via the `locals` option:\n\n```yml\n- client:\n    connection_options:\n      headers:\n        \"X-API-TOKEN\": \u003c%= token %\u003e\n```\n\n```ruby\ntoken = UserToken.generate\nWSDirector.run(scenario, url: \"ws://my.ws.server:4949/live\", locals: {token:})\n```\n\n### Protocols\n\nWSDirector uses protocols to handle provide convinient actions for some popular protocols.\n\n#### ActionCable Example\n\nChannel code:\n\n```ruby\nclass ChatChannel \u003c ApplicationCable::Channel\n  def subscribed\n    stream_from \"chat_test\"\n  end\n\n  def echo(data)\n    transmit data\n  end\n\n  def broadcast(data)\n    ActionCable.server.broadcast \"chat_test\", data\n  end\nend\n```\n\nScenario:\n\n```yml\n- client:\n    multiplier: \":scale\"\n    name: \"publisher\"\n    protocol: \"action_cable\"\n    actions:\n      - subscribe:\n          channel: \"ChatChannel\"\n      - wait_all\n      - perform:\n          channel: \"ChatChannel\"\n          action: \"broadcast\"\n          data:\n            text: \"hello\"\n- client:\n    name: \"listener\"\n    protocol: \"action_cable\"\n    actions:\n      - subscribe:\n          channel: \"ChatChannel\"\n      - wait_all\n      - receive:\n          channel: \"ChatChannel\"\n          data:\n            text: \"hello\"\n```\n\n#### Phoenix Channels\n\nWith \"phoenix\" protocol, you can use communicate with a [Phoenix Channels](https://hexdocs.pm/phoenix/channels.html) server:\n\n```yml\n- client:\n    protocol: phoenix\n    multiplier: \":scale\"\n    actions:\n      - join:\n          topic: room:lobby\n      - wait_all\n      - send:\n          topic: room:lobby\n          event: new_msg\n          data:\n            body: \"Hey from WS director!\"\n      - receive:\n          topic: room:lobby\n          multiplier: \":scale\"\n          event: new_msg\n          data:\n            body: \"Hey from WS director!\"\n```\n\n**IMPORTANT**: We support only v2 version of the Channels protocol.\n\n#### Custom protocols\n\nYou can define your own protocol and load it dynamically:\n\n```ruby\n# It's important to put a custom protocol class under WSDirector::Protocols\nmodule WSDirector::Protocols\n  class CustomProtocol \u003c Base\n    def send_ping_and_receive_pong\n      send(\"data\" =\u003e {\"type\" =\u003e \"ping\"})\n      receive(\"data\" =\u003e {\"type\" =\u003e \"pong\"})\n    end\n  end\nend\n```\n\nNow you can load it via the `-r` option:\n\n```sh\n$ wsdirector -u localhost:3232/ws -i '[\"send_ping_and_receive_pong\"]' -r ./path/to/custom_protocol.rb -vv\n\nhh:mm:ss client=default_1 Connecting\nhh:mm:ss client=default_1 Connected (45ms)\nhh:mm:ss client=default_1 Sent message: {\"type\":\"ping\"}\nhh:mm:ss client=default_1 Receive message: {\"type\":\"pong\"}\nhh:mm:ss client=default_1 Received message: {\"type\":\"pong\"} (21ms)\n```\n\n## Testing frameworks integration\n\nWSDirector does not provide any specific helpers for RSpec or Minitest. Instead, we provide an example setup, which you could adjust to your needs (and which is too small to be a part of the library).\n\nThe example below implies running tests against an Action Cable server with a token-based authentication\n\n```ruby\nmodule WSDirectorTestHelper\n  def run_websocket_scenario(path, token:, url: ActionCable.server.config.url, **options)\n    url = \"#{url}?jid=#{token}\"\n    scenario = Rails.root.join \"spec\" / \"fixtures\" / \"wsdirector\" / path\n\n    WSDirector.run(scenario, url:, **options)\n  end\nend\n\n# In RSpec, you can include this modules via the configuration\nRSpec.configure do |config|\n  # Here we only add this helper to system tests\n  config.include WSDirectorTestHelper, type: :system\nend\n```\n\n## Future Ideas\n\n- Report timings (per-client and aggregates)\n\n- What else? [Submit an issue!](https://github.com/palkan/wsdirector/issues/new)\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/palkan/wsdirector.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpalkan%2Fwsdirector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpalkan%2Fwsdirector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpalkan%2Fwsdirector/lists"}