#+up services for modules

1 messages ยท Page 1 of 1 (latest)

south lance
#

Was mostly just trying to solve the composition problem for reusable modules that typically need services like playwright going off of what we have now.

@cosmic bear and I talked through it and came up with a better plan that doesn't directly need to get a Service from an UpGroup. Something like:

  [modules.docusaurus]
  source = "github.com/example/docusaurus@v1.0"

  [modules.playwright]
  source = "github.com/example/playwright@v1.0"
  config.app = { from = "docusaurus:serve" }

It gets slightly more comlicated with Collections where the docusarus module could provide a +up for every site that exists in the Workspace, but as long as we can key on it this kind of approach should work.

cosmic bear
#

Yeah to summarize where my head is at:

  • Each playwright config needs a single target service to connect to. This is defined in the test framework itself.
  • The "test containers pattern" does NOT make sense for playwright, because the test framework has a builtin concept of service dependency, and abstracts it away from the test code. So the test code itself cannot orchestrate its test dependency (unlike general purpose test frameworks where there is no built-in concept of service dependency)
  • When running playwright in Dagger, we want the target service to map to a dagger service - obviously ๐Ÿ™‚ Let's call that target assignment
  • Target assignment cannot be automatically inferred: it requires manual configuration by the workspace owner. Eg. someone has to say "this playwright config must target this dagger service"
  • Target assignment should not be done in code. Because we want this use case to be possible without requiring a custom module
  • Target assignment should not be done in pseudo-code (configuration that has the complexity of code without the benefits).
  • A target assignment says "this playwright config should target this dagger service". Therefore it has 2 parts: a playwright config selector and a service selector.
  • The service selector syntax must uniquely identity a dagger service in the workspace in a single string.
  • The service selector syntax must be forward-compatible with modules-v2 and in particular with collections (as @south lance mentioned). So docusaurus:serve on its own is not enough (although maybe it could be a valid subset of the syntax?)
  • The playwright config selector could be a naive global selector in the beginning (basically a single constructor argument like @south lance showed above). But later will need to allow assigning different service targets to different playwright configs.

I think the hardest part, and the most important part to solve first, is the service selector syntax. Once we have that, we will have a reliable way to designate any service in a single string, without worrying about modules-v2 changes. Then we can figure out where to put that string - that can change over time, it's easier.

(sorry about the wall of text)

south lance
#

The selectors bit is interesting. I need to catch up on the state of the Collections work. The main point is that my selector must return 1 thing. So theoretically if I have 1 docusaurus site in my workspace and the docusarus module has 1 +up function, I could just use docusaurus as my selector

cosmic bear
#

Service selector

I want to add this API to modules-v2 (specifically the Artifacts part, which btw is the first in the rollout)

extend type Workspace {
  """Lookup a service matching the given selector. If no service matches the selector, return null"""
  service(selector: String!): Service
}

This ๐Ÿ‘† would be a convenience over the richer Workspace.artifacts() URL which allows filtering and querying all artifacts in the workspace - including finding which artifacts have a +up function, and the path to that function.

So we would also extend the artifacts API to support this selector. But for your use case, you would only need Workspace.service()

cosmic bear
#

It's like we already have the plumbing and query engine of sqlite, with an API, but no SQL syntax parser ๐Ÿ™‚

#

Could probably be useful in other places. Since it will be able to match any artifact, or specific action on an artifact - not just up

south lance
#

yeah makes sense to me!

cosmic bear
#

If you have this workspace:

type PlaywrightConfigs {
   keys: []WorkspacePath!
   get(key: WorkspacePath!): PlaywrightConfig
} @collection

type PlaywrightConfig {
   run: Void @check
}

type DocusaurusSites {
  keys: []WorkspacePath!
  get(key: WorkspacePath!): DocusaurusConfig
} @collection

type DocusaurusSite {
   devServer: Service! @up
}

Then the existing spec makes these commands possible:

$ dagger list docusaurus-sites
$ dagger list playwright-configs
$ dagger check --playwright-config=./e2e/docs-tests
$ dagger up --docusaurus-site=./docs
$ dagger up --docusaurus-site=./docs dev-server
#

So we could add a selector like:

$ dagger check --select="playwrightConfig == './e2e/docs-test'"
$ dagger up --select="docusaurusSite == './docs'"
$ dagger up --select="docusaurusSite == './docs' && fn == 'devServer''
#

From there, we would reuse the same selector syntax where it's needed.

For simple global config:

[modules.playwright]
config.defaultTarget = "docusaurusSite == './docs'"

For playwright-specific config:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  dagger: {
    target: "docusaurusSite == './docs' && fn == 'devServer'",
  },
  use: {
    baseURL: 'http://localhost:3000',
  },
  testDir: './tests',
});
south lance
#

Nice so then I come up with a way to put that selector in the playwright config or package.json, and then I can workspace.select(selector) and get my Service to bind

#

if we're ok with selectors in config.toml that's probably easier than made up keys in existing configs imo

#

since the selectors are relative to the other members of the same config.toml

cosmic bear
#

Well the selectors are relative to the current workspace.

#

So they make sense in any file within the workspace

south lance
#

relative was the wrong word, but what i meant is that they're shared concerns

cosmic bear
#

Tried an example snippet of piggybacking on playwright config above ๐Ÿ‘†

south lance
#

in this case I think it'll be hard to retrieve that value from a typescript file. We'd probably end up with a regex and if thats the case it could just as easily be a comment ๐Ÿคท

#

package.json is easier though. Something like { 'daggerService': 'docusaurusSite == "./docs" && fn == "devServer"' }

cosmic bear
#

But is it guaranteed that there will be a package.json next to that playwright.config.ts ?

south lance
#

According to claude, yes thats safe to assume

Based on your knowledge of playwright can it be assumed that a playwright.config.ts will
always be alongside a package.json?

โบ Yes. Playwright is an npm package โ€” you need @playwright/test in a package.json to use it. And playwright.config.ts is conventionally placed at the npm project root, same directory as package.json. There's no supported way to run Playwright without an npm project around it.

cosmic bear
#

OK then ๐Ÿ™‚

#

But is there a possibility that it will be the package.json for the app being tested?

#

That would make it weird in that case

#

Anyway this part is much more manageable. That's exactly what I mean when I said the service selector part is harder. I'm much less worried about us finding the right place to map to the playwright config (and change over time if needed)