Debugging, Testing, and Code QualityIntroductionWeb applications rarely fail because a developer cannot write a line of HTML, CSS, or JavaScript. They fail because small defects interact across documents, stylesheets, browsers, devices, data sources, and user actions. A missing closing tag may change the document structure; an incorrect CSS selector may make a button invisible; an unchecked JavaScript value may produce a runtime exception. Professional development therefore requires systematic debugging, testing, and quality practices rather than trial and error. Debugging is the process of locating, explaining, and correcting a defect. Testing is the planned evaluation of software to discover defects and verify expected behaviour. Code quality refers to characteristics such as correctness, readability, accessibility, security, performance, consistency, and maintainability. These activities are related but not identical: testing may reveal a failure, debugging investigates its cause, and refactoring improves the structure without intentionally changing behaviour. Learning ObjectivesBy the end of this chapter, a learner should be able to: - distinguish syntax, validation, runtime, logic, interface, and integration defects;
- use HTML and CSS validators and browser developer tools systematically;
- interpret JavaScript console messages, stack traces, and network information;
- apply defensive error handling without hiding defects;
- use linting, formatting, naming conventions, and documentation effectively;
- design basic unit, integration, and interface tests;
- refactor small web programs safely;
- evaluate maintainability, accessibility, and professional code quality.
1. Understanding Defects and FailuresA useful distinction is between an error, a defect, and a failure. An error is a human mistake, such as misunderstanding a requirement. The resulting incorrect code is a defect or bug. When the defect is executed or rendered and produces an observable problem, the result is a failure. 1.1 Common defect categories| Category | Typical example | Usual detection method |
|---|
| Syntax defect | Missing } or malformed JavaScript | Editor, parser, console | | Markup defect | Unclosed or incorrectly nested HTML element | HTML validator, DOM inspector | | Style defect | Selector or cascade rule has no intended effect | Styles panel, computed styles | | Runtime defect | Calling a method on null | Console, stack trace | | Logic defect | Total is calculated using the wrong condition | Unit test, code review | | Interface defect | Form works but gives no error feedback | Manual and automated UI testing | | Integration defect | Front end expects a field absent from an API response | Network tools, integration test | | Quality defect | Duplicated, unclear, or inaccessible implementation | Review, linting, audit |
A failure report should be precise. “The page is broken” is weak evidence. “Submitting the registration form with an empty email causes a 500 response and no visible message” identifies an input, action, observed result, and context. 1.2 Defect lifecycleA professional defect workflow commonly includes discovery, reproduction, isolation, correction, verification, and regression checking. Reproduction is especially important. If a developer cannot reproduce a problem, possible causes include different browser versions, data, screen sizes, timing, permissions, or cached resources. A minimal defect record should include: - a concise title;
- environment and browser information;
- preconditions and test data;
- exact reproduction steps;
- expected and actual results;
- console errors, screenshots, or network evidence;
- severity and priority;
- status and proposed correction.
Severity describes impact; priority describes urgency. A spelling error in a legal warning may have greater priority than a visually minor spacing issue, even if the latter is more noticeable. 2. A Systematic Debugging MethodDebugging is more reliable when treated as an investigation. Step 1: Observe and describeRecord what happened rather than immediately changing code. Identify the user action, input, browser, viewport, and expected behaviour. Step 2: Reproduce consistentlyUse the smallest reliable sequence that creates the failure. Reduce irrelevant variables: remove unrelated extensions, clear assumptions about stored data, and test with controlled input. Step 3: Form a hypothesisFor example, if a click does nothing, possible hypotheses include a missing event listener, an incorrect selector, a disabled element, an exception occurring before the handler, or an overlay intercepting the click. Step 4: Gather evidenceUse validators, breakpoints, logs, DOM inspection, computed styles, network requests, and test results. Evidence should test the hypothesis rather than merely confirm a guess. Step 5: Make the smallest justified changeChanging many files at once destroys information about which change mattered. A focused correction is easier to review and reverse. Step 6: Verify and perform regression testingConfirm the original failure is fixed, then test neighbouring behaviour. A correction to form validation must not prevent valid submissions or damage keyboard access. 2.1 Binary search through codeWhen a failure occurs in a long process, inspect intermediate states and divide the search area. For example, log whether the event occurred, whether data was read, whether validation passed, and whether rendering began. This narrows the defect location more efficiently than adding random logs throughout the program. 3. Validators and Browser Developer Tools3.1 HTML validationAn HTML validator checks conformance to the HTML specification. It can identify malformed nesting, duplicate attributes, invalid attributes, missing required information, and obsolete constructs. Validation does not prove that an application is usable or accessible. A document may be valid but have poor headings, vague link text, or inadequate keyboard support. Validation warnings should be interpreted rather than blindly eliminated. Some warnings indicate compatibility or maintainability concerns; others may be acceptable only in a documented context. Developers should validate the source document, not merely the DOM after scripts have modified it. 3.2 CSS validationA CSS validator detects malformed syntax, unknown properties, invalid values, and misplaced declarations. It cannot determine whether a declaration expresses the desired design. A valid rule may still be overridden by a more specific selector, hidden behind another element, or applied to the wrong element. The browser’s Styles panel is therefore essential. It shows matching rules, overridden declarations, source locations, and the computed value actually used. The box model view reveals content, padding, border, and margin dimensions. Computed styles are particularly useful when inheritance or the cascade produces unexpected results. 3.3 JavaScript console and debuggerThe console reports syntax and runtime errors and can display developer-generated messages. A stack trace identifies the sequence of function calls leading to an exception. The first application-level frame is often more useful than the final library frame. A debugger supports: - breakpoints that pause execution;
- conditional breakpoints that pause only for selected data;
- step over, step into, and step out commands;
- inspection of local variables and closure values;
- watch expressions;
- pause-on-exception behaviour.
A breakpoint should be placed near the earliest point where program state becomes incorrect. If a displayed price is wrong, inspect the parsed data and calculation before inspecting the final DOM update. 3.4 Network and application panelsThe Network panel helps identify incorrect URLs, HTTP status codes, redirects, blocked requests, slow responses, wrong methods, missing headers, and malformed response data. A successful request is not necessarily a successful operation: an HTTP response may contain an application-level error or unexpected schema. Storage and application tools expose cookies, local storage, session storage, service workers, and caches. Stale cached assets can make a correct code change appear ineffective. Developers should understand the effect of clearing storage or disabling cache during debugging. 3.5 Accessibility and responsive inspectionDeveloper tools can emulate screen sizes, device pixel ratios, reduced motion, and sometimes colour-vision conditions. Accessibility testing should also include keyboard-only navigation, visible focus, meaningful labels, sufficient contrast, and screen-reader-oriented semantic structure. Automated tools are useful for detecting common issues but cannot replace human evaluation. 4. Error Handling in JavaScriptError handling should make failures observable, understandable, and safe for users. It should not conceal defects by catching every exception and continuing as though nothing happened. 4.1 Validation and exceptionsValidation checks expected conditions before an operation. For example, a form can verify that a required value is present before sending a request. An exception represents an abnormal condition that prevents normal completion, such as an unavailable service or invalid response structure. function parseQuantity(value) {
const quantity = Number(value);
if (!Number.isInteger(quantity) || quantity < 1) {
throw new Error("Quantity must be a positive whole number");
}
return quantity;
}The function rejects invalid input at the boundary and communicates a specific reason. It does not silently convert an invalid value to zero. 4.2 try, catch, and finallyasync function loadProfile() {
const status = document.querySelector("#status");
status.textContent = "Loading…";
try {
const response = await fetch("/api/profile");
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const profile = await response.json();
renderProfile(profile);
} catch (error) {
console.error("Profile loading failed", error);
status.textContent = "The profile could not be loaded. Please try again.";
} finally {
document.body.classList.remove("is-loading");
}
}The catch block records technical information for diagnosis while presenting a safe, useful message to the user. The finally block performs cleanup regardless of success or failure. Sensitive information, tokens, and personal data should not be written to production logs. 4.3 Error boundaries and recoveryAt an interface level, a robust application should provide a recoverable state: retry a request, preserve user input, explain what action is possible, and avoid displaying raw stack traces. Errors should be handled at the layer that can make an appropriate decision. A low-level function may throw; a page controller may display a message. 5. Linting, Formatting, and Static Quality ControlsA linter performs static analysis without running the application. It can detect unused variables, unreachable code, accidental globals, inconsistent equality, suspicious conditions, and accessibility concerns. A formatter applies agreed layout rules such as indentation, quote style, line length, and spacing. Linting and formatting serve different purposes. Formatting resolves presentation differences automatically; linting identifies potentially incorrect or risky code. Neither proves correctness. A program can pass linting while implementing the wrong business rule. A practical project configuration should specify: - supported browser and language versions;
- indentation and naming conventions;
- permitted and prohibited language features;
- accessibility rules;
- handling of warnings and errors;
- commands that run locally and in continuous integration.
Quality checks should run before a change is merged. However, teams should avoid excessive rules that create noise. A warning that developers routinely ignore has little protective value. 6. Testing Web ApplicationsTesting compares observed behaviour with a defined expectation. A test should have a clear setup, action, and assertion. Test names should describe behaviour, not implementation details. 6.1 Test levels| Level | Scope | Example |
|---|
| Unit | Small function or module | Calculate a discounted total | | Integration | Several components together | Form submits data to an API adapter | | Interface or end-to-end | Complete user workflow | User signs in and sees a dashboard | | Acceptance | Requirement from stakeholder perspective | Invalid password produces an understandable message | | | |
Unit tests are fast and isolate causes well. Interface tests provide realistic confidence but are slower and more vulnerable to timing, data, and environment problems. A balanced test suite uses many focused tests and a smaller number of critical workflow tests. 6.2 Test cases and equivalence classesFor an input that accepts an integer from 1 to 10, useful equivalence classes include values below the range, values inside the range, values above the range, non-numeric values, and empty input. Boundary tests should include 0, 1, 10, and 11. The purpose is not to test every possible value, but to select representative cases where behaviour changes. 6.3 Unit test exampleexport function calculateTotal(price, quantity, discount = 0) {
if (price < 0 || quantity < 0 || discount < 0 || discount > 1) {
throw new RangeError("Invalid sale values");
}
return price * quantity * (1 - discount);
}Possible tests include: test("calculates a total without discount", () => {
expect(calculateTotal(20, 3)).toBe(60);
});
test("applies a fractional discount", () => {
expect(calculateTotal(50, 2, 0.10)).toBe(90);
});
test("rejects a discount above one", () => {
expect(() => calculateTotal(10, 1, 1.2)).toThrow(RangeError);
});The tests verify normal behaviour, a meaningful variation, and invalid input. Money calculations in production should consider currency precision and an appropriate representation; binary floating-point values may require careful rounding or integer minor units. 6.4 Interface testingInterface tests should verify observable outcomes rather than private implementation. A form test might locate a field by its label, enter invalid data, submit, and assert that an accessible error message appears and that submission is prevented. Tests should avoid fragile selectors based only on generated CSS classes. Important interface conditions include: - keyboard operation and focus order;
- empty, malformed, and boundary input;
- slow, failed, and successful network responses;
- different viewport widths;
- duplicate clicks and repeated submissions;
- loading, empty, error, and success states.
Asynchronous tests require explicit waiting for a meaningful condition, such as the appearance of a result, rather than arbitrary delays. 6.5 Regression and continuous integrationA regression test preserves a previously corrected behaviour. Continuous integration can run validation, linting, formatting checks, unit tests, and selected interface tests for every change. A failed pipeline is information, not an inconvenience to bypass. Teams should investigate flaky tests because unreliable tests reduce confidence in all results. 7. Refactoring and MaintainabilityRefactoring changes internal structure while preserving externally observable behaviour. Typical refactorings include extracting a function, replacing duplicated logic with a shared function, simplifying a condition, improving names, and separating data access from rendering. A safe refactoring cycle is: - establish a passing test or other observable baseline;
- make one structural change;
- run automated checks;
- inspect the diff;
- repeat in small steps.
Consider the following unclear code: if (u && u.a === 1) {
document.querySelector("#m").textContent = "OK";
}A clearer version may be: function isVerifiedUser(user) {
return Boolean(user) && user.accountStatus === "verified";
}
function showVerificationStatus(user) {
const message = document.querySelector("#verification-message");
message.textContent = isVerifiedUser(user) ? "Verified" : "Not verified";
}The second version uses meaningful names and separates a decision from a presentation effect. Refactoring is not automatically beneficial; unnecessary abstraction can make a small program harder to understand. The appropriate design depends on the project’s size, change rate, and team conventions. 7.1 Maintainability dimensionsMaintainable code is: - readable by someone other than its author;
- modular, with limited and clear dependencies;
- testable through stable interfaces;
- consistent with project conventions;
- observable through useful logs and diagnostics;
- documented where intent is not obvious;
- accessible and secure by design.
Comments should explain purpose, constraints, or decisions—not repeat what obvious code already says. Documentation should include setup instructions, scripts, supported environments, configuration, known limitations, and troubleshooting guidance. 8. Applied Case: An Illustrative Registration FormBackgroundA small training provider launches a registration page containing HTML fields for name and email, CSS for validation states, and JavaScript that sends data to /api/register. ProblemUsers report three symptoms: valid submissions sometimes show “Invalid email,” the submit button occasionally remains disabled, and on mobile the error text overlaps the form. AnalysisThe team first records browser and viewport details and reproduces the problems with boundary inputs. HTML validation reveals that the email field has no associated visible label. The debugger shows that the JavaScript reads input.value before trimming whitespace. A second inspection finds that the button is disabled during a request but is not re-enabled in the failure path. Network inspection shows that the server returns a structured 422 response for duplicate emails. Finally, responsive inspection reveals fixed-width error text and insufficient wrapping. Approach and solutionThe team associates labels with controls, trims input at the boundary, and separates client-side format checks from server-side uniqueness checks. The request code uses try...catch...finally, so the button is restored in every outcome. The interface displays a specific, accessible message for duplicate email and a general retry message for a service failure. CSS uses flexible sizing and allows error text to wrap. Tests cover empty input, leading and trailing spaces, malformed addresses, duplicate responses, successful responses, keyboard submission, and a narrow viewport. OutcomeThe original defects are corrected, and the regression suite prevents their return. More importantly, the team now has evidence for each correction rather than relying on visual inspection alone. Learning pointsThe case illustrates that a defect may cross layers. The email message involved input normalization, validation rules, server responses, state cleanup, accessibility, and responsive CSS. Effective diagnosis combines validators, developer tools, tests, and code review. 9. Limitations, Ethics, and Contemporary PracticeNo tool detects every defect. Validators may miss user-experience problems, unit tests may encode incorrect assumptions, and automated accessibility tools detect only a subset of barriers. Manual review, representative users, code review, and production monitoring remain necessary. Debugging data must be handled ethically. Do not expose passwords, authentication tokens, payment data, or unnecessary personal information in logs, screenshots, test fixtures, or bug reports. Use anonymised data and restrict access to diagnostic systems. Third-party analytics and error-monitoring tools should be evaluated for consent, retention, security, and legal compliance. Modern development increasingly uses automated dependency scanning, type checking, continuous integration, browser automation, and AI-assisted code generation. These tools can accelerate diagnosis and suggest tests, but generated code must be reviewed, executed in controlled environments, checked for security and licensing concerns, and tested against the actual requirements. An AI suggestion is not evidence of correctness. Best-Practice ChecklistBefore considering a web feature complete, a developer should ask: - Is the HTML valid, semantic, and keyboard accessible?
- Have CSS rules been checked in both normal and exceptional states?
- Are console errors absent and network failures handled?
- Are user inputs validated at appropriate boundaries?
- Are errors visible, understandable, and free of sensitive data?
- Do unit tests cover normal, boundary, and invalid cases?
- Does at least one realistic interface test cover the critical workflow?
- Have linting, formatting, and regression checks passed?
- Is the code named, structured, and documented for future maintainers?
Chapter SummaryDebugging, testing, and code quality are complementary professional practices. Defects should be classified, reproduced, investigated with evidence, corrected in small steps, and checked for regression. HTML and CSS validators identify conformance problems, while browser developer tools expose DOM structure, styles, execution state, network activity, and storage. JavaScript error handling should validate inputs, distinguish expected failures from programming defects, preserve useful diagnostics, and provide safe recovery. Linting and formatting establish consistent static quality controls but do not replace tests. Unit tests provide fast feedback about focused logic; integration and interface tests evaluate collaboration between components and realistic user workflows. Refactoring improves structure while preserving behaviour and should be protected by tests. Documentation, accessibility, privacy, security, and maintainability are part of code quality, not optional additions. Professional developers combine automated tools with human reasoning and ethical handling of diagnostic information. Key TermsDebugging: Systematic investigation and correction of software defects. Defect: An incorrect element of software caused by an error in design or implementation. Failure: An observable deviation from expected behaviour. Validator: A tool that checks conformance to a language or specification. Developer tools: Browser facilities for inspecting and diagnosing web applications. Stack trace: A record of function calls leading to an exception. Breakpoint: A programmed pause in execution for inspecting state. Computed style: The final CSS value applied to an element. Runtime error: A problem that occurs while code is executing. Exception: An abnormal condition that interrupts normal execution. Linting: Static analysis intended to identify suspicious or nonconforming code. Formatter: A tool that applies consistent source-code layout. Unit test: A test of a small, isolated program component. Integration test: A test of interacting components. Interface test: A test of behaviour through a user-facing interface. Regression test: A test that protects behaviour previously corrected or implemented. Refactoring: Restructuring code without intentionally changing its observable behaviour. Maintainability: The ease with which software can be understood, modified, tested, and supported. Review QuestionsShort Answer Questions- Distinguish an error, a defect, and a failure.
- What information should a useful defect report contain?
- State two purposes of browser developer tools.
- How do linting and formatting differ?
- Why is a
finally block useful in asynchronous JavaScript?
Long Answer Questions- Explain a systematic debugging workflow from reproduction through regression testing.
- Compare unit, integration, and interface testing, including the strengths and limitations of each.
- Explain how HTML and CSS validation should be combined with browser inspection.
- Describe defensive JavaScript error handling and explain why errors should not simply be hidden.
- Explain how refactoring, documentation, naming, and automated quality checks support maintainability.
Analytical / Application Questions- A form accepts valid input but remains disabled after a failed network request. Identify a likely design defect, describe how you would investigate it, and propose a test that prevents recurrence.
- A page passes HTML validation but is difficult to use with a keyboard and screen reader. Analyse why validation was insufficient and propose an evaluation plan.
- A team’s interface tests fail unpredictably because they use fixed delays and generated CSS selectors. Recommend a more reliable testing strategy and justify each change.
Multiple Choice Questions with AnswersQuestion 1Which tool is most directly suited to identifying a CSS declaration that is overridden by another rule? A. The browser Styles and Computed panels B. A JavaScript unit-test runner C. The server access log D. A package manager Correct Answer: A. The browser Styles and Computed panels Question 2What is the primary purpose of a stack trace? A. To measure page colour contrast B. To show the sequence of calls leading to an exception C. To format source code D. To validate HTML nesting Correct Answer: B. To show the sequence of calls leading to an exception Question 3Which statement best describes refactoring? A. Adding features without testing them B. Replacing all comments with automated tools C. Changing internal structure while preserving observable behaviour D. Deleting code that produces an error Correct Answer: C. Changing internal structure while preserving observable behaviour Question 4Why should a request button be restored in a finally block? A. The cleanup runs whether the request succeeds or fails B. It prevents all server errors C. It automatically validates HTML D. It guarantees a successful response Correct Answer: A. The cleanup runs whether the request succeeds or fails Question 5Which test level is most focused on a single calculation function? A. Acceptance testing B. Interface testing C. Unit testing D. Load testing Correct Answer: C. Unit testing Question 6A valid HTML document can still be inaccessible because validity does not guarantee: A. That the browser can parse it B. Good semantic structure and keyboard usability C. That elements have closing tags D. That attributes use legal syntax Correct Answer: B. Good semantic structure and keyboard usability Question 7What is the strongest reason to reproduce a defect before changing code? A. It makes the source file shorter B. It establishes reliable evidence about the failure C. It guarantees the defect is in JavaScript D. It removes the need for regression tests Correct Answer: B. It establishes reliable evidence about the failure Question 8Which test input strategy is especially useful for a value restricted to a range? A. Testing only the most common value B. Testing random colours C. Testing boundary and representative equivalence-class values D. Testing only empty input Correct Answer: C. Testing boundary and representative equivalence-class values Question 9Which practice is most appropriate for production diagnostic logs? A. Record passwords to simplify reproduction B. Include all personal data by default C. Avoid sensitive data and restrict access D. Disable all error logging Correct Answer: C. Avoid sensitive data and restrict access Question 10What is a limitation of automated accessibility tools? A. They cannot detect any markup errors B. They identify every usability barrier without human review C. They detect only a subset of accessibility problems D. They work only on server-side code Correct Answer: C. They detect only a subset of accessibility problems
|