qa-copilot-lab 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/bin/cli.js +11 -0
- package/image/1TB External SSD.jpg +0 -0
- package/image/Bluetooth Speaker.jpg +0 -0
- package/image/Ethernet Cable 3m.jpg +0 -0
- package/image/Laptop Stand.jpg +0 -0
- package/image/Mechanical Keyboard.jpg +0 -0
- package/image/Noise Cancelling Headphones.jpg +0 -0
- package/image/USB-C Hub.jpg +0 -0
- package/image/Webcam 1080p.jpg +0 -0
- package/image/Wi-Fi 6 Router.jpg +0 -0
- package/image/wireless mouse.jpg +0 -0
- package/mouse.jpg +0 -0
- package/package.json +26 -0
- package/public/css/style.css +169 -0
- package/public/images/products/1.jpg +0 -0
- package/public/images/products/10.jpg +0 -0
- package/public/images/products/2.jpg +0 -0
- package/public/images/products/3.jpg +0 -0
- package/public/images/products/4.jpg +0 -0
- package/public/images/products/5.jpg +0 -0
- package/public/images/products/6.jpg +0 -0
- package/public/images/products/7.jpg +0 -0
- package/public/images/products/8.jpg +0 -0
- package/public/images/products/9.jpg +0 -0
- package/public/js/accordion.js +10 -0
- package/public/js/autocomplete.js +39 -0
- package/public/js/cart.js +52 -0
- package/public/js/confirm-dialog.js +8 -0
- package/public/js/drag-drop.js +44 -0
- package/public/js/reveal-answer.js +10 -0
- package/public/js/star-rating.js +38 -0
- package/public/js/tabs.js +14 -0
- package/public/js/theme-toggle.js +24 -0
- package/public/js/toast.js +10 -0
- package/qa-copilot-lab-0.1.0.tgz +0 -0
- package/src/chaos.js +18 -0
- package/src/db.js +97 -0
- package/src/middleware/session.js +16 -0
- package/src/routes/api.js +136 -0
- package/src/routes/auth.js +45 -0
- package/src/routes/devApi.js +56 -0
- package/src/routes/pages.js +125 -0
- package/src/seed.js +43 -0
- package/src/server.js +71 -0
- package/src/state.js +33 -0
- package/views/404.ejs +7 -0
- package/views/account.ejs +42 -0
- package/views/cart.ejs +42 -0
- package/views/catalog.ejs +69 -0
- package/views/checkout.ejs +49 -0
- package/views/home.ejs +42 -0
- package/views/invoice.ejs +18 -0
- package/views/locators.ejs +323 -0
- package/views/login.ejs +22 -0
- package/views/orders.ejs +33 -0
- package/views/partials/footer.ejs +18 -0
- package/views/partials/head.ejs +9 -0
- package/views/partials/header.ejs +39 -0
- package/views/payment-frame.ejs +26 -0
- package/views/product.ejs +77 -0
- package/views/register.ejs +25 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<header class="navbar">
|
|
2
|
+
<a href="/" class="brand">QA Copilot Lab</a>
|
|
3
|
+
|
|
4
|
+
<form action="/catalog" method="get" class="search-form">
|
|
5
|
+
<input type="search" name="q" placeholder="Search products…" value="<%= typeof q !== 'undefined' ? q : '' %>" aria-label="Search products">
|
|
6
|
+
</form>
|
|
7
|
+
|
|
8
|
+
<nav class="nav-links">
|
|
9
|
+
<a href="/catalog">Catalog</a>
|
|
10
|
+
<a href="/locators">Locators</a>
|
|
11
|
+
</nav>
|
|
12
|
+
|
|
13
|
+
<% if (chaosMode !== 'off') { %>
|
|
14
|
+
<span class="chaos-badge" title="Chaos mode is scrambling selectors on purpose"><%= chaosMode %> chaos</span>
|
|
15
|
+
<% } %>
|
|
16
|
+
|
|
17
|
+
<a href="/cart" class="cart-link" data-testid="<%= tid('cart-link') %>">
|
|
18
|
+
Cart
|
|
19
|
+
<span class="badge" data-testid="<%= tid('cart-count') %>"><%= cartCount %></span>
|
|
20
|
+
</a>
|
|
21
|
+
|
|
22
|
+
<button type="button" class="theme-toggle" id="theme-toggle" title="Toggle dark mode" aria-label="Toggle dark mode">◐</button>
|
|
23
|
+
|
|
24
|
+
<div class="account-menu">
|
|
25
|
+
<% if (username) { %>
|
|
26
|
+
<span class="account-greeting" data-testid="<%= tid('account-menu') %>">Hi, <%= username %></span>
|
|
27
|
+
<a href="/account">Account</a>
|
|
28
|
+
<a href="/account/orders">Orders</a>
|
|
29
|
+
<form action="/logout" method="post"><button type="submit" class="link-button">Logout</button></form>
|
|
30
|
+
<% } else { %>
|
|
31
|
+
<a href="/login">Login</a>
|
|
32
|
+
<a href="/register">Register</a>
|
|
33
|
+
<% } %>
|
|
34
|
+
</div>
|
|
35
|
+
</header>
|
|
36
|
+
|
|
37
|
+
<div class="toast-stack" id="toast-stack" aria-live="polite"></div>
|
|
38
|
+
|
|
39
|
+
<main class="page-container">
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="light">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Payment</title>
|
|
6
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
7
|
+
</head>
|
|
8
|
+
<body class="frame-body">
|
|
9
|
+
<form class="payment-form">
|
|
10
|
+
<label for="card-number">Card number</label>
|
|
11
|
+
<input type="text" id="card-number" inputmode="numeric" placeholder="4242 4242 4242 4242" autocomplete="cc-number">
|
|
12
|
+
|
|
13
|
+
<div class="form-row">
|
|
14
|
+
<div>
|
|
15
|
+
<label for="card-expiry">Expiry</label>
|
|
16
|
+
<input type="text" id="card-expiry" placeholder="MM/YY" autocomplete="cc-exp">
|
|
17
|
+
</div>
|
|
18
|
+
<div>
|
|
19
|
+
<label for="card-cvc">CVC</label>
|
|
20
|
+
<input type="text" id="card-cvc" inputmode="numeric" placeholder="123" autocomplete="cc-csc">
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<p class="hint-text">This is a simulated payment field for practicing Playwright's frameLocator — no real card is ever charged.</p>
|
|
24
|
+
</form>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<%- include('partials/head', { title: product.name }) %>
|
|
2
|
+
<%- include('partials/header') %>
|
|
3
|
+
|
|
4
|
+
<div class="product-detail">
|
|
5
|
+
<div class="gallery">
|
|
6
|
+
<img src="/api/img/<%= product.id %>" alt="<%= product.name %> product photo" class="gallery-main" id="gallery-main">
|
|
7
|
+
<div class="gallery-thumbs">
|
|
8
|
+
<button type="button" class="thumb-btn" onclick="document.getElementById('gallery-main').style.filter='none'">Studio shot</button>
|
|
9
|
+
<button type="button" class="thumb-btn" onclick="document.getElementById('gallery-main').style.filter='grayscale(1)'">In use</button>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="product-info">
|
|
14
|
+
<h1><%= product.name %></h1>
|
|
15
|
+
<p class="category-tag"><%= product.category %></p>
|
|
16
|
+
|
|
17
|
+
<star-rating value="4"></star-rating>
|
|
18
|
+
|
|
19
|
+
<p class="price"><%= product.price.toFixed(2) %> USD</p>
|
|
20
|
+
|
|
21
|
+
<% if (product.stock === 0) { %>
|
|
22
|
+
<span class="badge badge-warn">Out of stock</span>
|
|
23
|
+
<% } else { %>
|
|
24
|
+
<div class="qty-stepper">
|
|
25
|
+
<button type="button" id="qty-minus" aria-label="Decrease quantity">−</button>
|
|
26
|
+
<input type="number" id="qty-input" value="1" min="1" max="<%= product.stock %>" aria-label="Quantity">
|
|
27
|
+
<button type="button" id="qty-plus" aria-label="Increase quantity">+</button>
|
|
28
|
+
</div>
|
|
29
|
+
<button type="button"
|
|
30
|
+
class="btn btn-primary <%= cx('add-to-cart') %>"
|
|
31
|
+
data-role="add-to-cart"
|
|
32
|
+
data-testid="<%= tid('add-to-cart-' + product.id) %>"
|
|
33
|
+
data-product-id="<%= product.id %>"
|
|
34
|
+
data-product-name="<%= product.name %>"
|
|
35
|
+
data-qty-input="qty-input">
|
|
36
|
+
Add to cart
|
|
37
|
+
</button>
|
|
38
|
+
<% } %>
|
|
39
|
+
|
|
40
|
+
<% if (username) { %>
|
|
41
|
+
<form action="/wishlist" method="post" class="inline-form">
|
|
42
|
+
<input type="hidden" name="productId" value="<%= product.id %>">
|
|
43
|
+
<button type="submit" class="btn btn-secondary">Save to wishlist</button>
|
|
44
|
+
</form>
|
|
45
|
+
<% } %>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="tabs" data-component="tabs">
|
|
50
|
+
<div role="tablist" aria-label="Product information">
|
|
51
|
+
<button role="tab" id="tab-desc" aria-controls="panel-desc" aria-selected="true" data-testid="<%= tid('tab-description') %>">Description</button>
|
|
52
|
+
<button role="tab" id="tab-reviews" aria-controls="panel-reviews" aria-selected="false" data-testid="<%= tid('tab-reviews') %>">Reviews (<%= reviews.length %>)</button>
|
|
53
|
+
<button role="tab" id="tab-shipping" aria-controls="panel-shipping" aria-selected="false" data-testid="<%= tid('tab-shipping') %>">Shipping</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div role="tabpanel" id="panel-desc" aria-labelledby="tab-desc">
|
|
57
|
+
<p>The <%= product.name %> is part of our <%= product.category %> lineup — reliable, well reviewed, and always in stock somewhere in the warehouse (usually).</p>
|
|
58
|
+
</div>
|
|
59
|
+
<div role="tabpanel" id="panel-reviews" aria-labelledby="tab-reviews" hidden>
|
|
60
|
+
<ul class="review-list">
|
|
61
|
+
<% reviews.forEach(function(r) { %>
|
|
62
|
+
<li class="review-item">
|
|
63
|
+
<strong><%= r.author %></strong> — <%= r.rating %>/5
|
|
64
|
+
<p><%= r.body %></p>
|
|
65
|
+
</li>
|
|
66
|
+
<% }); %>
|
|
67
|
+
<% if (reviews.length === 0) { %>
|
|
68
|
+
<li class="review-item">No reviews yet for this product.</li>
|
|
69
|
+
<% } %>
|
|
70
|
+
</ul>
|
|
71
|
+
</div>
|
|
72
|
+
<div role="tabpanel" id="panel-shipping" aria-labelledby="tab-shipping" hidden>
|
|
73
|
+
<p>Ships within 2 business days. Free shipping on orders over $50.</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<%- include('partials/footer') %>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<%- include('partials/head', { title: 'Register' }) %>
|
|
2
|
+
<%- include('partials/header') %>
|
|
3
|
+
|
|
4
|
+
<div class="auth-form-wrap">
|
|
5
|
+
<h1>Create an account</h1>
|
|
6
|
+
<% if (error === 'taken') { %>
|
|
7
|
+
<p class="form-error" role="alert">That username is already taken.</p>
|
|
8
|
+
<% } else if (error === 'missing') { %>
|
|
9
|
+
<p class="form-error" role="alert">Please fill in every field.</p>
|
|
10
|
+
<% } %>
|
|
11
|
+
<form method="post" action="/register" class="auth-form">
|
|
12
|
+
<label for="username">Username</label>
|
|
13
|
+
<input type="text" id="username" name="username" required autocomplete="username">
|
|
14
|
+
|
|
15
|
+
<label for="email">Email</label>
|
|
16
|
+
<input type="email" id="email" name="email" required autocomplete="email">
|
|
17
|
+
|
|
18
|
+
<label for="password">Password</label>
|
|
19
|
+
<input type="password" id="password" name="password" required autocomplete="new-password">
|
|
20
|
+
|
|
21
|
+
<button type="submit" class="btn btn-primary" data-testid="<%= tid('register-submit') %>">Create account</button>
|
|
22
|
+
</form>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<%- include('partials/footer') %>
|