All Projects → holochain → tryorama

holochain / tryorama

Licence: other
Toolset to manage Holochain conductors and facilitate test scenarios

Programming Languages

typescript
32286 projects
rust
11053 projects

Projects that are alternatives of or similar to tryorama

Nextjs Headless Wordpress
🔥 Nextjs Headless WordPress
Stars: ✭ 110 (+292.86%)
Mutual labels:  e2e-tests
Angular1 Webpack Starter
Component based Angular(1.x) web development with Webpack and ES6.
Stars: ✭ 148 (+428.57%)
Mutual labels:  e2e-tests
React Native Swiper Flatlist
👆 Swiper component implemented with FlatList using Hooks & Typescript + strict automation tests with Detox
Stars: ✭ 217 (+675%)
Mutual labels:  e2e-tests
Sakuli
Sakuli is an end-2-end testing and monitoring tool for web sites and common UIs with multiple monitoring integrations
Stars: ✭ 115 (+310.71%)
Mutual labels:  e2e-tests
Javascript Testing Best Practices
📗🌐 🚢 Comprehensive and exhaustive JavaScript & Node.js testing best practices (August 2021)
Stars: ✭ 13,976 (+49814.29%)
Mutual labels:  e2e-tests
Cypress Example Recipes
Various recipes for testing common scenarios with Cypress
Stars: ✭ 2,485 (+8775%)
Mutual labels:  e2e-tests
Testing Workshop
A workshop for learning how to test JavaScript applications
Stars: ✭ 1,276 (+4457.14%)
Mutual labels:  e2e-tests
playwright-ci
☁️ Set up Playwright in CI
Stars: ✭ 27 (-3.57%)
Mutual labels:  e2e-tests
Cypress Example Todomvc
The official TodoMVC tests written in Cypress.
Stars: ✭ 143 (+410.71%)
Mutual labels:  e2e-tests
Softest
Recording Browser Interactions And Generating Test Scripts.
Stars: ✭ 208 (+642.86%)
Mutual labels:  e2e-tests
Php Sf Flex Webpack Encore Vuejs
A simple app skeleton to try to make every components work together : symfony 4 (latest stable at the date, but work with sf 3.3+ if you just change the versions in composer.json), symfony/flex, webpack-encore, vuejs 2.5.x, boostrap 4 sass
Stars: ✭ 118 (+321.43%)
Mutual labels:  e2e-tests
Cypress Schematic
Add cypress to an Angular CLI project
Stars: ✭ 132 (+371.43%)
Mutual labels:  e2e-tests
Seleniumbase
A Python framework that inspires developers to become better test automation engineers. 🧠💡
Stars: ✭ 2,520 (+8900%)
Mutual labels:  e2e-tests
Protractor Net
The .NET port of Protractor, an E2E test framework for Angular apps
Stars: ✭ 113 (+303.57%)
Mutual labels:  e2e-tests
Pending Xhr Puppeteer
Small tool to wait that all xhr are finished in puppeteer
Stars: ✭ 227 (+710.71%)
Mutual labels:  e2e-tests
Npm Registry Browser
Browse the npm registry with an SPA made in React, with full dev workflow.
Stars: ✭ 97 (+246.43%)
Mutual labels:  e2e-tests
Express Typescript Boilerplate
A delightful way to building a RESTful API with NodeJs & TypeScript by @w3tecch
Stars: ✭ 2,293 (+8089.29%)
Mutual labels:  e2e-tests
softest
Recording Browser Interactions And Generating Test Scripts.
Stars: ✭ 225 (+703.57%)
Mutual labels:  e2e-tests
Qawolf
🐺 Create browser tests 10x faster
Stars: ✭ 2,912 (+10300%)
Mutual labels:  e2e-tests
Root Cause
🔍 Root Cause is a tool for troubleshooting Puppeteer and Playwright tests. 🔎
Stars: ✭ 205 (+632.14%)
Mutual labels:  e2e-tests

Project Discord License: CAL 1.0 Test

Tryorama

Tryorama provides a convenient way to run an arbitrary amount of Holochain conductors on your local machine, as well as on network nodes that are running the TryCP service. In combination with the test runner and assertion library of your choice, you can test the behavior of multiple Holochain nodes in a network. Included functions to clean up used resources make sure that all state is deleted between tests so that they are independent of one another.

npm install @holochain/tryorama

Complete API reference

Example

With a few lines of code you can start testing your Holochain application. This example uses tape as test runner and assertion library. You can choose any other runner and library.

import { ActionHash, DnaSource } from "@holochain/client";
import { pause, runScenario, Scenario } from "@holochain/tryorama";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import test from "tape-promise/tape.js";

test("Create 2 players and create and read an entry", async (t) => {
  await runScenario(async (scenario: Scenario) => {
    // Construct proper paths for your DNAs.
    // This assumes DNA files created by the `hc dna pack` command.
    const testDnaPath = dirname(fileURLToPath(import.meta.url)) + "/test.dna";

    // Set up the array of DNAs to be installed, which only consists of the
    // test DNA referenced by path.
    const dnas: DnaSource[] = [{ path: testDnaPath }];

    // Add 2 players with the test DNA to the Scenario. The returned players
    // can be destructured.
    const [alice, bob] = await scenario.addPlayersWithHapps([dnas, dnas]);

    // Shortcut peer discovery through gossip and register all agents in every
    // conductor of the scenario.
    await scenario.shareAllAgents();

    // Content to be passed to the zome function that create an entry,
    const content = "Hello Tryorama";

    // The cells of the installed hApp are returned in the same order as the DNAs
    // that were passed into the player creation.
    const createEntryHash: ActionHash = await alice.cells[0].callZome({
      zome_name: "coordinator",
      fn_name: "create",
      payload: content,
    });

    // Wait for the created entry to be propagated to the other node.
    await pause(100);

    // Using the same cell and zome as before, the second player reads the
    // created entry.
    const readContent: typeof content = await bob.cells[0].callZome({
      zome_name: "coordinator",
      fn_name: "read",
      payload: createEntryHash,
    });
    t.equal(readContent, content);
  });
});

Have a look at the tests for many more examples.

Example without wrapper

Written out without the wrapper function, the same example looks like this:

import { ActionHash, DnaSource } from "@holochain/client";
import { pause, Scenario } from "@holochain/tryorama";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import test from "tape-promise/tape.js";

test("Create 2 players and create and read an entry", async (t) => {
  const testDnaPath = dirname(fileURLToPath(import.meta.url)) + "/test.dna";
  const dnas: DnaSource[] = [{ path: testDnaPath }];

  // Create an empty scenario.
  const scenario = new Scenario();
  const [alice, bob] = await scenario.addPlayersWithHapps([dnas, dnas]);

  await scenario.shareAllAgents();

  const content = "Hello Tryorama";
  const createEntryHash: EntryHash = await alice.cells[0].callZome({
    zome_name: "coordinator",
    fn_name: "create",
    payload: content,
  });

  await pause(100);

  const readContent: typeof content = await bob.cells[0].callZome({
    zome_name: "coordinator",
    fn_name: "read",
    payload: createEntryHash,
  });
  t.equal(readContent, content);

  // Shut down all conductors and delete their files and directories.
  await scenario.cleanUp();
});

The wrapper takes care of creating a scenario and shutting down or deleting all conductors involved in the test scenario.

Error handling with test runners like tape

When writing the test, it might be necessary to handle errors while developing, depending on the test runner. With a test runner like "tape", uncaught errors will cause the conductor process and therefore the test to hang or output [object Object] as the only error message. In this case errors can be handled like this:

const scenario = new LocalScenario();
try {
  /* scenario operations */
} catch (error) {
  console.error("error occurred during test", error);
} finally (
  await scenario.cleanUp()
}

Logging

The log level can be set with the environment variable TRYORAMA_LOG_LEVEL. Log levels used in Tryorama are debug, verbose and info. The default level is info. To set the log level for a test run, prepend the test command with:

TRYORAMA_LOG_LEVEL=debug node test.js

Concepts

Scenarios provide high-level functions to interact with the Holochain Conductor API. Players consist of a conductor, an agent and installed hApps, and can be added to a Scenario. Access to installed hApps is made available through the cells, which can either be destructured according to the sequence during installation or retrieved by their role id.

One level underneath the Scenario is the Conductor. Apart from methods for creation, startup and shutdown, it comes with complete functionality of Admin and App API that the JavaScript client offers.

Conductor example

Here is the above example that just uses a Conductor without a Scenario:

  const testDnaPath = dirname(fileURLToPath(import.meta.url)) + "/test.dna";
  const dnas: DnaSource[] = [{ path: testDnaPath }];

  const conductor1 = await createConductor();
  const conductor2 = await createConductor();
  const [aliceHapps, bobHapps] = await conductor1.installAgentsHapps({
    agentsDnas: [dnas, dnas],
  });

  await addAllAgentsToAllConductors([conductor1, conductor2]);

  const entryContent = "test-content";
  const createEntryHash: EntryHash = await aliceHapps.cells[0].callZome({
    zome_name: "coordinator",
    fn_name: "create",
    payload: entryContent,
  });

  await pause(100);

  const readEntryResponse: typeof entryContent =
    await bobHapps.cells[0].callZome({
      zome_name: "coordinator",
      fn_name: "read",
      payload: createEntryHash,
    });

  await conductor1.shutDown();
  await conductor2.shutDown();
  await cleanAllConductors();

Note that you need to set a network seed manually when registering DNAs. This is taken care of automatically when using a Scenario.

hApp Installation

Conductors are equipped with a method for easy hApp installation, installAgentsHapps. It has a almost identical signature to Scenario.addPlayers and takes an array of DNAs for each agent, resulting in a 2-dimensional array, e. g. [[agent1dna1, agent1dna2], [agent2dna1], [agent3dna1, agent3dna2, agent3dna3]].

const testDnaPath = dirname(fileURLToPath(import.meta.url)) + "/test.dna";
const dnas: DnaSource[] = [{ path: testDnaPath }];

const conductor = await createLocalConductor();
const [aliceHapps] = await conductor.installAgentsHapps({
  agentsDnas: [dnas],
});

const entryContent = "test-content";
const createEntryHash: EntryHash = await aliceHapps.cells[0].callZome({
  zome_name: "coordinator",
  fn_name: "create",
  payload: entryContent,
});

await conductor.shutDown();

Convenience function for Zome calls

When testing a Zome, there are usually a lot of calls to the cell with this particular Zome. Specifying the Cell and the Zome name for every call is repetitive. It is therefore convenient to use a handle to a particular combination of Cell and Zome.

Instead of

const [aliceHapps] = await conductor.installAgentsHapps({
  agentsDnas: [dnas],
});
const createEntryHash: EntryHash = await aliceHapps.cells[0].callZome({
  zome_name: "coordinator",
  fn_name: "create",
  payload: entryContent,
});
const readEntryHash: string = await aliceHapps.cells[0].callZome({
  zome_name: "coordinator",
  fn_name: "read",
  payload: createEntryHash,
});

the shorthand access to the Zome can be called

const [aliceHapps] = await conductor.installAgentsHapps({
  agentsDnas: [dnas],
});
const aliceCallCoordinatorZome = getZomeCaller(aliceHapps.cells[0], "coordinator");
const entryHeaderHash: ActionHash = await aliceCallCoordinatorZome(
  "create",
  "test-entry"
);
const readEntryHash: string = await aliceCallCoordinatorZome(
  "read",
  entryHeaderHash
);

Signals

Scenario.addPlayerWithHapp as well as Conductor.installAgentsHapps allow for a signal handler to be specified. Signal handlers are registered with the conductor and act as a callback when a signal is received.

const scenario = new Scenario();
const testDnaPath = dirname(fileURLToPath(import.meta.url)) + "/test.dna";
const dnas: Dna[] = [{ source: { path: testDnaPath } }];

let signalHandler: AppSignalCb | undefined;
const signalReceived = new Promise<AppSignal>((resolve) => {
  signalHandler = (signal) => {
    resolve(signal);
  };
});

const alice = await scenario.addPlayerWithHapp({ dnas, signalHandler });

const signal = { value: "hello alice" };
alice.cells[0].callZome({
  zome_name: "coordinator",
  fn_name: "signal_loopback",
  payload: signal,
});

const actualSignalAlice = await signalReceived;
t.deepEqual(actualSignalAlice.data.payload, signal);

await scenario.cleanUp();
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].