diff --git a/e2e/basic.spec.ts b/e2e/basic.spec.ts
index 36b8d793f6687fa21e826900eea365ac0b2cb815..dbaeb4b9b718f5770e58b919ec1ef44b6a602bb3 100644
--- a/e2e/basic.spec.ts
+++ b/e2e/basic.spec.ts
@@ -1,6 +1,6 @@
 /* eslint-disable turbo/no-undeclared-env-vars */
 import { expect, test } from "@playwright/test";
-import { exec } from "child_process";
+import { ChildProcess, exec } from "child_process";
 import { execSync } from "node:child_process";
 import crypto from "node:crypto";
 import { mkdir } from "node:fs/promises";
@@ -21,109 +21,149 @@ test.beforeEach(async () => {
   await mkdir(cwd, { recursive: true });
 });
 
-const templateTypes: TemplateType[] = ["streaming", "simple"];
-const templateFrameworks: TemplateFramework[] = ["nextjs", "express"];
-const templateEngines: TemplateEngine[] = ["simple", "context"];
-const templateUIs: TemplateUI[] = ["shadcn", "html"];
+type AppType = "--frontend" | "--no-frontend" | "";
+
+const templateTypes: TemplateType[] = ["streaming"];
+const templateFrameworks: TemplateFramework[] = ["express"];
+const templateEngines: TemplateEngine[] = ["simple"];
+const templateUIs: TemplateUI[] = ["html"];
+const MODEL = "gpt-3.5-turbo";
 
 for (const templateType of templateTypes) {
   for (const templateFramework of templateFrameworks) {
     for (const templateEngine of templateEngines) {
       for (const templateUI of templateUIs) {
-        const shouldGenerateFrontendEnum =
+        const appType: AppType =
           templateFramework === "express" || templateFramework === "fastapi"
-            ? ["--frontend", "--no-frontend"]
-            : [""];
-        for (const shouldGenerateFrontend of shouldGenerateFrontendEnum) {
-          if (templateEngine === "context") {
-            // we don't test context templates because it needs OPEN_AI_KEY
-            continue;
-          }
-          test(`try create-llama ${templateType} ${templateFramework} ${templateEngine} ${templateUI} ${shouldGenerateFrontend}`, async ({
-            page,
-          }) => {
-            const createLlama = fileURLToPath(
-              new URL("../dist/index.js", import.meta.url),
-            );
+            ? "--frontend"
+            : "";
+        if (templateEngine === "context") {
+          // we don't test context templates because it needs OPEN_AI_KEY
+          continue;
+        }
+        test(`try create-llama ${templateType} ${templateFramework} ${templateEngine} ${templateUI} ${appType}`, async ({
+          page,
+        }) => {
+          const name = runCreateLlama(
+            templateType,
+            templateFramework,
+            templateEngine,
+            templateUI,
+            appType,
+          );
 
-            const name = [
-              templateType,
-              templateFramework,
-              templateEngine,
-              templateUI,
-              shouldGenerateFrontend,
-            ].join("-");
-            const command = [
-              "node",
-              createLlama,
-              name,
-              "--template",
-              templateType,
-              "--framework",
-              templateFramework,
-              "--engine",
-              templateEngine,
-              "--ui",
-              templateUI,
-              "--open-ai-key",
-              process.env.OPEN_AI_KEY || "",
-              shouldGenerateFrontend,
-              "--eslint",
-            ].join(" ");
-            console.log(`running command '${command}' in ${cwd}`);
-            execSync(command, {
-              stdio: "inherit",
-              cwd,
-            });
+          const port = Math.floor(Math.random() * 10000) + 10000;
+          const cps = await runApp(name, appType, port);
+
+          // test frontend
+          await page.goto(`http://localhost:${port}`);
+          await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
+          // TODO: test backend using curl (would need OpenAI key)
+          // clean processes
+          cps.forEach((cp) => cp.kill());
+        });
+      }
+    }
+  }
+}
 
-            const port = Math.floor(Math.random() * 10000) + 10000;
+async function runApp(
+  name: string,
+  appType: AppType,
+  port: number,
+): Promise<ChildProcess[]> {
+  const cps = [];
 
-            if (
-              shouldGenerateFrontend === "--frontend" &&
-              templateFramework === "express"
-            ) {
-              execSync("npm install", {
-                stdio: "inherit",
-                cwd: `${cwd}/${name}/frontend`,
-              });
-              execSync("npm install", {
-                stdio: "inherit",
-                cwd: `${cwd}/${name}/backend`,
-              });
-            } else {
-              execSync("npm install", {
-                stdio: "inherit",
-                cwd: `${cwd}/${name}`,
-              });
-            }
+  try {
+    switch (appType) {
+      case "--no-frontend":
+        cps.push(
+          await createProcess("npm run dev", `${cwd}/${name}/backend`, port),
+        );
+        break;
+      case "--frontend":
+        cps.push(
+          await createProcess(
+            "npm run dev",
+            `${cwd}/${name}/backend`,
+            port + 1,
+          ),
+        );
+        cps.push(
+          await createProcess("npm run dev", `${cwd}/${name}/frontend`, port),
+        );
+        break;
+      default:
+        cps.push(await createProcess("npm run dev", `${cwd}/${name}`, port));
+        break;
+    }
+  } catch (e) {
+    cps.forEach((cp) => cp.kill());
+    throw e;
+  }
+  return cps;
+}
 
-            if (shouldGenerateFrontend === "--no-frontend") {
-              return;
-            }
+async function createProcess(command: string, cwd: string, port: number) {
+  const cp = exec(command, {
+    cwd,
+    env: {
+      ...process.env,
+      PORT: `${port}`,
+    },
+  });
+  if (!cp) throw new Error(`Can't start process ${command} in ${cwd}`);
 
-            const cp = exec("npm run dev", {
-              cwd:
-                shouldGenerateFrontend === "--frontend"
-                  ? `${cwd}/${name}/frontend`
-                  : `${cwd}/${name}`,
-              env: {
-                ...process.env,
-                PORT: `${port}`,
-              },
-            });
+  await waitPort({
+    host: "localhost",
+    port,
+    timeout: 1000 * 60,
+  });
+  return cp;
+}
 
-            await waitPort({
-              host: "localhost",
-              port,
-              timeout: 1000 * 60,
-            });
+function runCreateLlama(
+  templateType: string,
+  templateFramework: string,
+  templateEngine: string,
+  templateUI: string,
+  appType: AppType,
+) {
+  const createLlama = fileURLToPath(
+    new URL("../dist/index.js", import.meta.url),
+  );
 
-            await page.goto(`http://localhost:${port}`);
-            await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
-            cp.kill();
-          });
-        }
-      }
-    }
-  }
+  const name = [
+    templateType,
+    templateFramework,
+    templateEngine,
+    templateUI,
+    appType,
+  ].join("-");
+  const command = [
+    "node",
+    createLlama,
+    name,
+    "--template",
+    templateType,
+    "--framework",
+    templateFramework,
+    "--engine",
+    templateEngine,
+    "--ui",
+    templateUI,
+    "--model",
+    MODEL,
+    "--open-ai-key",
+    "testKey", // TODO: pass in OPEN_AI_KEY from CI env if needed for tests
+    appType,
+    "--eslint",
+    "--use-npm",
+  ].join(" ");
+  console.log(`running command '${command}' in ${cwd}`);
+  execSync(command, {
+    stdio: "inherit",
+    cwd,
+  });
+  return name;
 }
diff --git a/package.json b/package.json
index f0079ecbc156fd6175317c46de6bf7d96bfc1c23..3d77e5f2a539c10e79df5bc7c0ba14e00ebf5d17 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
     "dev": "ncc build ./index.ts -w -o dist/",
     "build": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
     "lint": "eslint . --ignore-pattern dist",
-    "e2e": "playwright test",
+    "e2e": "playwright test --reporter=list",
     "prepublishOnly": "cd ../../ && turbo run build"
   },
   "devDependencies": {