#Cannot run React app as a service

1 messages · Page 1 of 1 (latest)

craggy oyster
#

Hey guys!
I'm trying to run a React app as a service, which is meant to bind to a test container (playwright). My code looks like:

reactApp := FrontendContainer.
    WithExposedPort(3000).
    AsService(dagger.ContainerAsServiceOpts{Args: []string{"npm", "run", "start"}})

test := PlaywrightContainer.
    WithServiceBinding("ui", reactApp).
    WithExec([]string{"sh", "-c", "npm run test; echo -n $? > /src/exit_code"}).
    Stdout(ctx)

I can see that the npm run start is executed successful. But the test execution is failing with ERR_CONNECTION_REFUSED:

Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:3000/
    Call log:
      - navigating to "http://localhost:3000/", waiting until "load"

I'm using Dagger 0.15.1. Do you know what is the issue here?

craggy oyster
#

Found the problem.
I have to switch the base url in the tests to: APP_BASE_URL=http://ui:3000/

#

[SOLVED] Cannot run React app as a service

frosty saddle
#

Hi Patrick 👋 Glad to see you!

craggy oyster
#

Ok the Connection is working. But found out that the app is showing a white Page.

#

@frosty saddle we have now the First Team who is using Dagger in their project. 🎉

craggy oyster
#

Cannot run React app as a service

craggy oyster
#

There is an issue.
I see that the connection is no longer refused but all my tests are failing waiting for locator('[name=\'loginfmt\']') to be visible. Every screenshot of a failure shows me nothing.

synergizerUI := FrontendContainer.
    WithExposedPort(3000).
    AsService(dagger.ContainerAsServiceOpts{Args: []string{"npm", "run", "start"}})

return PlaywrightContainer.
    WithServiceBinding("ui", synergizerUI).
    WithExec([]string{"npm", "run", "test"}), nil

https://v3.dagger.cloud/innovation/traces/f1c9b6b43b9482c1cec47e6b45742836

craggy oyster
#

@frosty saddle do you have any idea why this is not working or what I am missing?

muted hinge
#

i.e dagger call ui up and try to access it from localhost

craggy oyster
# muted hinge i.e` dagger call ui up` and try to access it from `localhost`

I tried it out and it is working.

func (m *Synergizer) Ui(source *dagger.Directory, token *dagger.Secret) *dagger.Service {
    return dag.Container().
        From("node:18.17-slim").
        WithDirectory("/src", source).
        WithSecretVariable("USER_PASSWORD_BASE64", token).
        WithWorkdir("/src/").
        WithExec([]string{"npm", "install"}).
        WithExposedPort(3000).
        AsService(dagger.ContainerAsServiceOpts{Args: []string{"npm", "start"}})
}
#

dagger call ui --source=. --token=env:USER_PASSWORD_BASE64 up

muted hinge
#

maybe running npm build && npm serve might be better for this playwright case?

craggy oyster
#

I will check that

craggy oyster
#

@muted hinge interesting. The command npm serve tells me accept connections from localhost:3000
All tests are failing because of connection refused

craggy oyster
#

Ok connection is fixed. Seems that is not able to browse
TimeoutError: locator.waitFor: Timeout 15000ms exceeded.
Call log:
- waiting for locator('[name='loginfmt']') to be visible

frosty saddle
craggy oyster
#

Could it be that the execution is much slower ? I will try to Extend the timeout.

muted hinge
#

@craggy oyster any chance you can try running curl against the service and see if you get any output?

muted hinge
#

I can try putting together a simple repro but I'm missing the playwright thing. Is that module public Patrick?

craggy oyster
# muted hinge <@960764294851608606> any chance you can try running `curl` against the service ...

So the curl command gives me that back:

<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Synergizer</title><script defer="defer" src="/static/js/main.d673e6df.js"></script><link href="/static/css/main.6f138c3c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
craggy oyster
# muted hinge I can try putting together a simple repro but I'm missing the playwright thing. ...

Here is my function which is using playwright:

func (m *Synergizer) Get(ctx context.Context, source *dagger.Directory, token *dagger.Secret) (*dagger.File, error) {
    return dag.Container().
        From("mcr.microsoft.com/playwright:v1.45.3-noble").
        WithDirectory("/src", source).
        WithMountedCache("/root/.npm", dag.CacheVolume("node")).
        WithWorkdir("/src/e2e-tests/").
        WithExec([]string{"npm", "install"}).
        WithServiceBinding("ui", m.Ui(source, token)).
        WithExec([]string{"sh", "-c", "npm run test; echo -n $? > /src/exit_code"}).
        File("screenshot.png"), nil
}
muted hinge
#

hey @craggy oyster ! everything seemed to have worked for me

#

here's my pipeline:

type ViteProject struct{}

func (m *ViteProject) Get(ctx context.Context, source *dagger.Directory) *dagger.Container {
    return dag.Container().
        From("mcr.microsoft.com/playwright:v1.50.0-noble").
        WithDirectory("/src", source).
        WithMountedCache("/root/.npm", dag.CacheVolume("node")).
        WithWorkdir("/src").
        WithExec([]string{"npm", "install"}).
        WithServiceBinding("ui", m.Ui(source)).
        WithExec([]string{"npm", "run", "test"})
}

func (m *ViteProject) Ui(src *dagger.Directory) *dagger.Service {
    return dag.Container().From("node:22-alpine").
        WithMountedDirectory("/src", src).
        WithWorkdir("/src").
        WithExec([]string{"npm", "install"}).
        WithExposedPort(5173).
        AsService(dagger.ContainerAsServiceOpts{Args: []string{"npm", "run", "dev", "--", "--host"}})

}
#

my react application is a vite project that I created with npm create vite@latest

#

I've tried creating the react app via npx create-react-app but seems like cra is broken and the installation never finishes

craggy oyster
muted hinge
craggy oyster
#

Hmm my setup is not working

#

It feels like the App is not running.
I don’t get it at the moment.

muted hinge
#

It also works when you start the service via Dagger and connect to it locally

#

Have you tried starting the service with Dagger and then running npm run test in your local machine but setting the URL to the Dagger service?

craggy oyster
#

And it seems that here is the problem.
When I start the test. Playwright navigates to the base url and then a redirect is happening for SSO-Login. This login is not working. The loading is extremely slow.

craggy oyster
#

If I run the test in firefox on my local machine setting the URL to the dagger service it is working.
But if I run the test only with dagger it is not working.

#

TimeoutError: locator.waitFor: Timeout 45000ms exceeded.
Call log:
- waiting for locator('[name='loginfmt']') to be visible

#

That is really strange

muted hinge
#

you could run that container with --net host so it can still access to your Dagger UI service

craggy oyster
#

I think the Server is not ready.
There must be a way to wait until the server is complete ready.

muted hinge
#

is that some react command?

craggy oyster
#

react Script start

muted hinge
craggy oyster
#

I think about
npm run build,
and then npm serve build &
and then I have to wait until it is ready to accept Connections…

craggy oyster
muted hinge
#

and then I have to wait until it is ready to accept Connections…

dagger checks for this automatically with the WithExposedPort

#

dagger probes the port and will only continue when the port is listening

muted hinge
craggy oyster
#

Ok let me try that in one function tomorrow.

craggy oyster
#

ha it is also not running. To be honest, I don't get it.

craggy oyster
#

Ok the service seems to be up and running

muted hinge
craggy oyster
muted hinge
#

😠 .. ok. Let me quickly download a CRA example app and try it myself

craggy oyster
#

I changed my code to this:

func (m *Synergizer) Services(ctx context.Context, source *dagger.Directory, token *dagger.Secret) (string, error) {
    srvFrontend := dag.Container().
        From("node:18.17-slim").
        WithDirectory("/src", source).
        WithSecretVariable("USER_PASSWORD_BASE64", token).
        WithWorkdir("/src").
        WithExposedPort(3000).
        AsService(dagger.ContainerAsServiceOpts{Args: []string{"npm", "--prefix", "synergizer-ui", "run", "serve"}})

    srvFrontend, err := srvFrontend.Start(ctx)
    if err != nil {
        return "", err
    }

    defer srvFrontend.Stop(ctx)

    return dag.Container().
        From("mcr.microsoft.com/playwright:v1.45.3-noble").
        WithDirectory("/src", source).
        WithWorkdir("/src/e2e-tests").
        WithExec([]string{"npm", "install"}).
        WithServiceBinding("ui", srvFrontend).
        WithExec([]string{"sh", "-c", "npm run test; echo -n $? > /src/exit_code"}).
        Stdout(ctx)
}
#

In my PW Config i have as baseURL http://localhost:3000/

craggy oyster
#

Ok, I wiill check if the redirect url is configured right in ms entra

craggy oyster
#

localhost:3000 should work.

craggy oyster
#

Could it be that the frontend is Not able to browse the web?

muted hinge
#

@craggy oyster how would you manage this when running playwright in your complete pipeline

#

?

#

specially if you're planning run this in CI 🤔

#

because in that case localhost:3000 is not accessible

#

e2e tests like these IMO they usually bypass the auth flow and use some sort of impersonarion system to run.

craggy oyster
#

Normally our Gitlab Runner can access the internet. I will try that.

muted hinge
craggy oyster
#

good point1

muted hinge
#

since the react app is running within Dagger and it's not exposing any port locally, there's no way for the auth callback to reach that server

#

that's why generally for CI integration tests, auth flows usually get stubbed or something

craggy oyster
#

I will Share my complete gitlab Pipeline tomorrow.
I refactored it to unblock my team.

craggy oyster
#

Hey 👋 @muted hinge & @frosty saddle,
as I promised I will share my actual refactored pipeline where the sso login is working.
This is my .gitlab.yml file:

include:
  - /common-rules.yml
  - /synergizer-ui/ci/pipeline.yml
  - /e2e-tests/ci/pipeline.yml

stages:
  - lint
  - test
  - run-e2e-tests

This is pipeline.yml file for the ui:

lint:
  rules:
    - !reference [.is_merge_request, rules]
  tags:
    - dind
  image: node:18.17-slim
  script:
    - cd synergizer-ui
    - npm install
    - npm run lint

test:
  rules:
    - !reference [.is_merge_request, rules]
  tags:
    - dind
  image: node:18.17-slim
  script:
    - cd synergizer-ui
    - npm install
    - npm run test

And finally this is my pipeline.yml file for the e2e-tests:

e2e-tests:
  rules:
    - !reference [.is_merge_request, rules]
  tags:
    - dind
  stage: run-e2e-tests
  image: mcr.microsoft.com/playwright:v1.45.3-noble
  needs:
    - lint
  before_script:
    - ./e2e-tests/ci/prepare-e2e-tests.sh
    - ./e2e-tests/ci/setup_local_environment.sh
  script:
    - echo 'Start running e2e-tests'
    - echo "Running e2e-tests in docker environment"
    - ./e2e-tests/ci/run-e2e-tests.sh
  variables:
    APP_BASE_URL: http://localhost:3000
  artifacts:
    when: always
    reports:
      junit: ./e2e-tests/results.xml
    paths:
      - ./e2e-tests/setup.log
      - ./e2e-tests/screenplay-report.txt
      - ./e2e-tests/screenplay-report/
      - ./synergizer-ui.log
      - ./e2e-tests/test-results/
      - ./e2e-tests/playwright-report/
  cache:
    key:
      files:
        - e2e-tests/package-lock.json
    paths:
      - e2e-tests/node_modules/
muted hinge
#

@craggy oyster so IIUC you removed Dagger to unblock your team?

craggy oyster
#

At the moment yes.
We committed us to use dagger in our project but I dont want to be blocking part.
If we find a solution to run this with dagger it would be great.

craggy oyster
muted hinge
#

now that I've checked the code I have a high degree of certainty about what the issue might be. Your SSO login app redirects the user to localhost:3000 probably to continue the authentication process. The thing is that when you're running this in Dagger localhost:3000 is no such thing. @craggy oyster

#

@craggy oyster do you have the permissions to configure your SSO app by any chance?

craggy oyster
muted hinge
craggy oyster
muted hinge
#

@craggy oyster curious if setting something like http://localhost-dagger:3000 would work

#

since the message says "it should start with" http://localhost

craggy oyster
#

What can we do if this is not possible?

frosty saddle
#

In cases like that I've bound the service to "localhost". It works in the cases I've run into but feels like something that should be avoided unless necessary

muted hinge
#

mostly the reason I suggested trying the localhost-dagger approach 🙏

frosty saddle
#

hm did that change? I've 100% used that in the past successfully

muted hinge
frosty saddle
muted hinge
#

here's the code:

// Returns a container that echoes whatever string argument is provided
func (m *Lala) Test() *dagger.Container {
    return dag.Container().From("alpine").
        WithServiceBinding("localhost", dag.Container().From("nginx").WithExposedPort(80).AsService()).
        WithExec([]string{"ping", "localhost"})

}
frosty saddle
#

hm yeah i'm not sure

craggy oyster
muted hinge
#

@craggy oyster just wondering if thelocalhost-dagger think worked 🤞

craggy oyster
#

Give me a second

#

It is not possible to define localhost-dagger. It is not allowed…

#

We have to find another way.

muted hinge
muted hinge
#

@craggy oyster haven't forgotten about you 🙏

craggy oyster
#

And ideas how we can solve the issue?

craggy oyster
#

@muted hinge & @frosty saddle
I tried again this time with localhost

func (m *Synergizer) Ui(source *dagger.Directory, token *dagger.Secret) *dagger.Service {
    return dag.Container().
        From("node:18.17-slim").
        WithDirectory("/src", source).
        WithSecretVariable("USER_PASSWORD_BASE64", token).
        WithWorkdir("/src/synergizer-ui/").
        WithExec([]string{"npm", "install"}).
        WithExec([]string{"npm", "run", "build:app"}).
        WithExposedPort(3000).
        AsService(dagger.ContainerAsServiceOpts{Args: []string{"npm", "run", "serve"}})
}

func (m *Synergizer) Get(ctx context.Context, source *dagger.Directory, token *dagger.Secret) (string, error) {
    return dag.Container().
        From("mcr.microsoft.com/playwright:v1.45.3-noble").
        WithDirectory("/src", source).
        WithMountedCache("/root/.npm", dag.CacheVolume("node")).
        WithWorkdir("/src/e2e-tests/").
        WithExec([]string{"npm", "install"}).
        WithServiceBinding("localhost", m.Ui(source, token)).
        WithExec([]string{"sh", "-c", "npm run test; echo -n $? > /src/exit_code"}).
        Stdout(ctx)
}

In my test I try to navigate:

Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:3000/                                                                                                                                        
    Call log:                                                                                                                                                                                                        - navigating to "http://localhost:3000/", waiting until "load"
#

In the serve command I see the output: expecting connection at http://localhost:3000