diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 0408e04..7cc4ea3 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -165,6 +165,46 @@ jobs: cd frontend bun run lint + playwright: + runs-on: ubuntu-latest + name: Playwright + needs: changed_files + container: + image: git.kjan.de/actions/runner-casino-playwright:latest + if: ${{ needs.changed_files.outputs.frontend == 'true' || needs.changed_files.outputs.workflow == 'true' }} + steps: + - name: Checkout Code + uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: "temurin" # See 'Supported distributions' for available options + java-version: "23" + - name: Install bun + uses: oven-sh/setup-bun@v2 + - uses: actions/cache@v4 + working-directory: ./frontend + with: + path: | + frontend/node_modules/ + key: ${{ runner.os }}-bun- + restore-keys: | + ${{ runner.os }}-bun- + - run: bun add -g concurrently + - name: Install dependencies + run: | + cd frontend + bun install + - uses: actions/setup-node@v4 + with: + node-version: 22.12 + working-directory: ./frontend + - name: Run Playwright tests + env: + CI: true + SPRING_PROFILES_ACTIVE: inmemory + working-directory: ./frontend + run: bash -c "source $HOME/.cargo/env && bunx playwright test" + oxlint: runs-on: docker name: oxlint diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 548c9aa..3704255 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6") implementation("org.springframework.boot:spring-boot-starter-mail") + runtimeOnly("com.h2database:h2") } tasks.withType { diff --git a/backend/src/main/java/de/szut/casino/CasinoApplication.java b/backend/src/main/java/de/szut/casino/CasinoApplication.java index 9e1f3b2..7d73081 100644 --- a/backend/src/main/java/de/szut/casino/CasinoApplication.java +++ b/backend/src/main/java/de/szut/casino/CasinoApplication.java @@ -49,33 +49,32 @@ public class CasinoApplication { lootBoxRepository.saveAll(Arrays.asList(basicLootBox, premiumLootBox)); RewardEntity commonReward = new RewardEntity(); - commonReward.setValue(new BigDecimal("0.50")); + commonReward.setRewardValue(new BigDecimal("0.50")); commonReward.setProbability(new BigDecimal("0.7")); RewardEntity rareReward = new RewardEntity(); - rareReward.setValue(new BigDecimal("2.00")); + rareReward.setRewardValue(new BigDecimal("2.00")); rareReward.setProbability(new BigDecimal("0.25")); RewardEntity epicReward = new RewardEntity(); - epicReward.setValue(new BigDecimal("5.00")); + epicReward.setRewardValue(new BigDecimal("5.00")); epicReward.setProbability(new BigDecimal("0.5")); RewardEntity premiumCommon = new RewardEntity(); - premiumCommon.setValue(new BigDecimal("2.00")); + premiumCommon.setRewardValue(new BigDecimal("2.00")); premiumCommon.setProbability(new BigDecimal("0.6")); RewardEntity premiumRare = new RewardEntity(); - premiumRare.setValue(new BigDecimal("5.00")); + premiumRare.setRewardValue(new BigDecimal("5.00")); premiumRare.setProbability(new BigDecimal("0.3")); RewardEntity legendaryReward = new RewardEntity(); - legendaryReward.setValue(new BigDecimal("15.00")); + legendaryReward.setRewardValue(new BigDecimal("15.00")); legendaryReward.setProbability(new BigDecimal("0.10")); rewardRepository.saveAll(Arrays.asList( commonReward, rareReward, epicReward, - premiumCommon, premiumRare, legendaryReward - )); + premiumCommon, premiumRare, legendaryReward)); basicLootBox.getRewards().add(commonReward); basicLootBox.getRewards().add(premiumRare); diff --git a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxService.java b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxService.java index 647b69b..cf1ea7d 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxService.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxService.java @@ -34,7 +34,7 @@ public class LootBoxService { public void handleBalance(UserEntity user, LootBoxEntity lootBox, RewardEntity reward) { user.setBalance(user.getBalance().subtract(lootBox.getPrice())); - user.setBalance(user.getBalance().add(reward.getValue())); + user.setBalance(user.getBalance().add(reward.getRewardValue())); userRepository.save(user); } } diff --git a/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java b/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java index 1abd2df..a2fd65a 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java @@ -17,7 +17,7 @@ import java.util.List; public class RewardEntity { public RewardEntity(BigDecimal value, BigDecimal probability) { - this.value = value; + this.rewardValue = value; this.probability = probability; } @@ -26,7 +26,7 @@ public class RewardEntity { private Long id; @Column(precision = 19, scale = 2) - private BigDecimal value; + private BigDecimal rewardValue; @Column(precision = 5, scale = 2) private BigDecimal probability; diff --git a/backend/src/main/resources/application-inmemory.properties b/backend/src/main/resources/application-inmemory.properties new file mode 100644 index 0000000..878fc39 --- /dev/null +++ b/backend/src/main/resources/application-inmemory.properties @@ -0,0 +1,58 @@ +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +spring.jpa.hibernate.ddl-auto=create-drop + +server.port=${HTTP_PORT:8080} +stripe.secret.key=${STRIPE_SECRET_KEY:sk_test_51QrePYIvCfqz7ANgqam8rEwWcMeKiLOof3j6SCMgu2sl4sESP45DJxca16mWcYo1sQaiBv32CMR6Z4AAAGQPCJo300ubuZKO8I} +stripe.webhook.secret=${STRIPE_WEBHOOK_SECRET:whsec_746b6a488665f6057118bdb4a2b32f4916f16c277109eeaed5e8f8e8b81b8c15} + +app.frontend-host=${FE_URL:http://localhost:4200} + +app.mail.authentication=${MAIL_AUTHENTICATION:false} +app.mail.host=${MAIL_HOST:localhost} +app.mail.port=${MAIL_PORT:1025} +app.mail.username=${MAIL_USER:null} +app.mail.password=${MAIL_PASS:null} +app.mail.from-address=${MAIL_FROM:casino@localhost} +app.mail.protocol=${MAIL_PROTOCOL:smtp} + +spring.application.name=casino + +# JWT Configuration +jwt.secret=${JWT_SECRET:5367566B59703373367639792F423F4528482B4D6251655468576D5A71347437} +jwt.expiration.ms=${JWT_EXPIRATION_MS:86400000} + +# Logging +logging.level.org.springframework.security=DEBUG + +# Swagger +springdoc.swagger-ui.path=swagger +springdoc.swagger-ui.try-it-out-enabled=true + +# GitHub OAuth2 Configuration +spring.security.oauth2.client.registration.github.client-id=${GITHUB_CLIENT_ID:Ov23lingzZsPn1wwACoK} +spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET:4b327fb3b1ab67584a03bcb9d53fa6439fbccad7} +spring.security.oauth2.client.registration.github.redirect-uri=${app.frontend-host}/oauth2/callback/github +spring.security.oauth2.client.registration.github.scope=user:email,read:user +spring.security.oauth2.client.provider.github.authorization-uri=https://github.com/login/oauth/authorize +spring.security.oauth2.client.provider.github.token-uri=https://github.com/login/oauth/access_token +spring.security.oauth2.client.provider.github.user-info-uri=https://api.github.com/user +spring.security.oauth2.client.provider.github.user-name-attribute=login + +# OAuth Success and Failure URLs +app.oauth2.authorizedRedirectUris=${app.frontend-host}/auth/oauth2/callback + +# Google OAuth2 Configuration +spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID:350791038883-c1r7v4o793itq8a0rh7dut7itm7uneam.apps.googleusercontent.com} +spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET: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} +spring.security.oauth2.client.registration.google.redirect-uri=${app.frontend-host}/oauth2/callback/google +spring.security.oauth2.client.registration.google.scope=email,profile +spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth +spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token +spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo +spring.security.oauth2.client.provider.google.user-name-attribute=sub + diff --git a/frontend/.gitignore b/frontend/.gitignore index cc7b141..b93e819 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -29,6 +29,9 @@ yarn-error.log .history/* # Miscellaneous +/.claude +/test-results +/playwright-report /.angular/cache .sass-cache/ /connect.lock diff --git a/frontend/bun.lock b/frontend/bun.lock index 048e212..83c43eb 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -32,6 +32,7 @@ "@angular-devkit/build-angular": "^20.0.0", "@angular/cli": "^20.0.0", "@angular/compiler-cli": "^20.0.0", + "@playwright/test": "^1.52.0", "@types/jasmine": "~5.1.0", "angular-eslint": "19.7.1", "eslint": "^9.28.0", @@ -542,6 +543,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@playwright/test": ["@playwright/test@1.52.0", "", { "dependencies": { "playwright": "1.52.0" }, "bin": { "playwright": "cli.js" } }, "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.2", "", { "os": "android", "cpu": "arm" }, "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.2", "", { "os": "android", "cpu": "arm64" }, "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw=="], @@ -1082,7 +1085,7 @@ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -1514,6 +1517,10 @@ "piscina": ["piscina@5.0.0", "", { "optionalDependencies": { "@napi-rs/nice": "^1.0.1" } }, "sha512-R+arufwL7sZvGjAhSMK3TfH55YdGOqhpKXkcwQJr432AAnJX/xxX19PA4QisrmJ+BTTfZVggaz6HexbkQq1l1Q=="], + "playwright": ["playwright@1.52.0", "", { "dependencies": { "playwright-core": "1.52.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw=="], + + "playwright-core": ["playwright-core@1.52.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg=="], + "postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="], "postcss-loader": ["postcss-loader@8.1.1", "", { "dependencies": { "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", "semver": "^7.5.4" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ=="], @@ -2102,6 +2109,8 @@ "resolve-url-loader/loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw=="], + "rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "schema-utils/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -2140,6 +2149,8 @@ "tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], "webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], @@ -2266,6 +2277,8 @@ "karma-coverage/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "karma/chokidar/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "karma/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "karma/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -2306,6 +2319,8 @@ "tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "webpack-dev-server/chokidar/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "webpack-dev-server/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "webpack-dev-server/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], diff --git a/frontend/e2e/backend.spec.ts b/frontend/e2e/backend.spec.ts new file mode 100644 index 0000000..4279610 --- /dev/null +++ b/frontend/e2e/backend.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test'; + +test('backend works', async ({ page }) => { + await page.goto('http://localhost:8080/health'); + const response = await page.textContent('body'); + expect(response).toBeTruthy(); + expect(page.getByText('{"status":"UP"}')).toBeVisible(); +}); diff --git a/frontend/e2e/homepage.spec.ts b/frontend/e2e/homepage.spec.ts new file mode 100644 index 0000000..8eae80d --- /dev/null +++ b/frontend/e2e/homepage.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from '@playwright/test'; + +test('home page loads correctly', async ({ page }) => { + await page.goto('/'); + + await expect(page).toHaveTitle(/Casino/); + await expect(page.getByRole('heading', { name: 'Willkommensbonus' })).toBeVisible(); + await expect(page.getByText('von bis zu €')).toBeVisible(); +}); + +test('registration popup should open and close', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'Registrieren' }).click(); + + await expect(page.getByText('Konto erstellenE-')).toBeVisible(); + + await page.getByRole('button', { name: 'Dialog schließen' }).click(); + + await expect(page.getByText('Konto erstellenE-')).not.toBeVisible(); +}); + +test('registration should work', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'Registrieren' }).click(); + + await page.getByRole('textbox', { name: 'E-Mail' }).fill('test@kjan.email'); + await page.getByRole('textbox', { name: 'Benutzername' }).fill('test-playwright'); + await page.getByRole('textbox', { name: 'Passwort' }).fill('BananaMan123'); + await page.locator('form').getByRole('button', { name: 'Registrieren' }).click(); + await page.getByRole('button', { name: 'Dialog schließen' }).click(); + await page.getByRole('navigation').getByRole('button', { name: 'Anmelden' }).click(); + await page.getByRole('textbox', { name: 'Benutzername oder E-Mail' }).fill('test@kjan.email'); + await page.getByRole('textbox', { name: 'Passwort' }).fill('BananaMan123'); + await page.locator('form').getByRole('button', { name: 'Anmelden' }).click(); + await expect(page.getByText('Email not verified')).toBeVisible(); +}); diff --git a/frontend/package.json b/frontend/package.json index 5a9aa3c..1acbf48 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,9 +39,10 @@ "tslib": "^2.3.0" }, "devDependencies": { - "@angular/compiler-cli": "^20.0.0", "@angular-devkit/build-angular": "^20.0.0", "@angular/cli": "^20.0.0", + "@angular/compiler-cli": "^20.0.0", + "@playwright/test": "^1.52.0", "@types/jasmine": "~5.1.0", "angular-eslint": "19.7.1", "eslint": "^9.28.0", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000..8d1e8c7 --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,49 @@ +// playwright.config.ts (or .js) +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + // This baseURL is for your frontend tests. + // Tests hitting the backend directly will use absolute URLs. + baseURL: 'http://localhost:4200', + trace: 'on-first-retry', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + ], + + webServer: { + command: + 'cd .. && conc -n "frontend,backend" "cd frontend && bun run start" "cd backend/ && watchexec -r -e java ./gradlew :bootRun"', + // **IMPORTANT CHANGE HERE:** + // Point to your backend's health check endpoint. + // If your Spring Boot app uses Actuator, it might be /actuator/health + // Verify the correct health endpoint for your backend. + url: 'http://localhost:8080/health', // Or "http://localhost:8080/actuator/health" + reuseExistingServer: !process.env.CI, + // **INCREASE TIMEOUT SIGNIFICANTLY** + // Gradle + Spring Boot can take a while, especially on first run or in CI. + // Adjust as needed, e.g., 3-5 minutes. + timeout: 300 * 1000, // 300 seconds = 5 minutes + stdout: 'pipe', // Good for capturing logs in CI reports + stderr: 'pipe', + // Optional: If your server needs specific environment variables + // env: { + // SPRING_PROFILES_ACTIVE: 'test', // Example for Spring Boot + // }, + }, +}); diff --git a/justfile b/justfile index 4fc777c..c574ea0 100644 --- a/justfile +++ b/justfile @@ -6,8 +6,7 @@ info: start: command -v concurrently &> /dev/null || bun add -g concurrently command -v watchexec &> /dev/null || brew install watchexec - docker compose up -d - conc -n "frontend,backend" "cd frontend && bun run start" "cd backend/ && watchexec -r -e java ./gradlew :bootRun" + conc -n "frontend,backend,docker" "cd frontend && bun run start" "cd backend/ && watchexec -r -e java ./gradlew :bootRun" "docker compose up" # Builds both the backend and frontend docker images (obv) build: