HTML, CSS & JavaScript

Chapter 13
Debugging, Testing, Performance and Security

Certificate in Full stack programming
HTML, CSS & JavaScript   •   Certificate

Debugging, Testing, Performance and Security

Introduction

A web application is not complete when it merely displays a page or responds to a click. Professional development also requires evidence that the application behaves correctly, loads efficiently, remains maintainable, and protects users and data. These concerns are closely connected. A performance optimisation can introduce a defect; a security control can affect usability; and a test can reveal a design weakness rather than only a coding error.

This chapter presents a practical quality-assurance workflow for HTML, CSS, and JavaScript applications. It introduces browser developer tools, debugging techniques, automated testing, validation and linting, performance optimisation, and essential client-side security practices. The emphasis is appropriate for certificate-level study: students should be able to investigate problems systematically, implement basic tests, and recognise common security risks.

Learning objectives

By the end of this chapter, you should be able to:

  • use browser developer tools to inspect, debug, and profile a web page;
  • distinguish syntax, runtime, logic, usability, and security defects;
  • write basic automated unit and browser tests;
  • validate HTML and CSS and use JavaScript linting effectively;
  • improve loading performance through asset, rendering, and network decisions;
  • identify cross-site scripting, injection, unsafe DOM use, and dependency risks;
  • apply safer client-side coding practices and explain their limitations;
  • combine testing, debugging, performance review, and security checks into a repeatable workflow.

1. A quality model for front-end applications

Quality is multidimensional. A useful inspection model includes the following dimensions:

DimensionGuiding questionTypical evidence
CorrectnessDoes the application produce the intended result?Tests, requirements, observed behaviour
ReliabilityDoes it continue to work under expected conditions?Error handling, repeated tests
Usability and accessibilityCan intended users operate it effectively?Keyboard tests, semantic HTML, user observation
PerformanceDoes it respond and render quickly enough?Network and performance measurements
SecurityCan untrusted input or dependencies cause harm?Code review, threat analysis, security tests
MaintainabilityCan another developer understand and change it?Linting, structure, documentation

A defect is any departure from a required or expected property. A failure is the observable incorrect behaviour caused by a defect. For example, an incorrect selector is a defect; a missing navigation style in the browser is a failure. Debugging seeks the cause, whereas testing seeks evidence about behaviour.

A practical quality cycle is:

  1. reproduce the problem;
  2. isolate the smallest relevant component;
  3. form a hypothesis about the cause;
  4. inspect evidence using tools or tests;
  5. change one thing at a time;
  6. verify the correction and run regression tests;
  7. record the cause and prevention lesson.

This process is more dependable than changing several files until the symptom disappears.

2. Debugging with browser developer tools

Modern browsers provide a development environment commonly opened with a context-menu command such as Inspect. Although interfaces differ, the main panels have similar purposes.

2.1 Elements and styles

The Elements or Inspector panel displays the live Document Object Model (DOM), which may differ from the original HTML after JavaScript changes it. It helps developers:

  • inspect semantic structure and attributes;
  • find the element matched by a selector;
  • enable or disable CSS declarations temporarily;
  • examine computed styles, inheritance, box dimensions, and layout;
  • identify overflowing, hidden, or incorrectly positioned content.

Temporary changes in the inspector do not modify the source file. A common debugging mistake is to see a successful temporary style change and forget to copy the intended correction into the project.

2.2 Console

The Console reports syntax errors, uncaught exceptions, warnings, and messages written by console.log() and related methods. A useful log should identify relevant state without exposing passwords, tokens, or personal information. console.error() communicates failure, but logging is not a substitute for handling an error.

An exception usually contains a message and a stack trace. The stack trace identifies the sequence of function calls leading to the failure and often links to a source location. Read the first meaningful application frame rather than assuming the last visible line is the original cause.

2.3 Sources and breakpoints

The Sources panel permits stepping through JavaScript. A breakpoint pauses execution before a selected line runs. While paused, a developer can inspect:

  • local and global variables;
  • the call stack;
  • function arguments;
  • the current value of expressions;
  • whether a condition is true.

Conditional breakpoints are valuable when a loop fails only for a particular record. Watch expressions and step-over, step-into, and step-out controls help reveal control flow. Do not leave accidental breakpoints or verbose diagnostic logging in production builds.

2.4 Network panel

The Network panel records requests for documents, scripts, stylesheets, fonts, images, and data. Important observations include status code, transfer size, resource size, timing, caching, request priority, and blocking relationships. A page can appear slow because of a slow server response, a large asset, too many requests, render-blocking CSS, or JavaScript that occupies the main thread.

An HTTP status code is evidence, not a complete diagnosis. A 404 indicates that a resource was not found; a 200 response may still contain the wrong content. Inspect response headers and content as well as status.

2.5 Performance and memory panels

The Performance panel records activity such as scripting, style calculation, layout, painting, and compositing. Long tasks can delay input and visual updates. Repeated layout changes mixed with reads can cause inefficient layout work, sometimes called layout thrashing. The Memory panel can help identify objects that remain reachable after they should have been released, such as event listeners attached to removed elements.

Accessibility inspection should also be part of debugging. Check keyboard focus, accessible names, headings, colour contrast, form labels, and messages that are communicated to assistive technology. Automated tools identify common problems but cannot replace human interaction testing.

3. A disciplined debugging example

Suppose a shopping list button appears to do nothing. A useful investigation is:

  1. Confirm the problem and record the exact steps.
  2. Inspect the button and verify its id, type, and position in the DOM.
  3. Check the Console for a syntax or null-reference error.
  4. Set a breakpoint in the click handler.
  5. Determine whether the handler runs, whether the input value is correct, and whether the new item is created.
  6. Inspect the final DOM and CSS if the item exists but is invisible.
  7. Test an empty value, repeated values, keyboard activation, and page reload.

If the handler is absent, the likely issue is event registration or a script loading failure. If it runs but accesses a missing element, the selector or script timing is suspect. If the DOM changes but the item is not visible, CSS or rendering is the likely layer. This layer-by-layer method reduces guesswork.

4. Testing web applications

Testing compares observed behaviour with an explicit expectation. Tests should be small, repeatable, independent where possible, and meaningful to users or requirements.

4.1 Test levels

  • Unit tests examine a small function or module, such as a price calculation or input-normalisation function.
  • Integration tests examine cooperation between components, such as a form and its validation logic.
  • End-to-end or browser tests operate the application through a browser and check a user journey.
  • Manual exploratory tests use human observation to discover unexpected behaviour, especially usability and accessibility issues.

A test should normally arrange the situation, act on the system, and assert the result. This is often called the Arrange–Act–Assert pattern.

4.2 Example unit test

The following function has no DOM dependency, which makes it easy to test:

export function totalPrice(price, quantity) {
  if (!Number.isFinite(price) || !Number.isInteger(quantity) || quantity < 0) {
    throw new Error('Invalid order values');
  }
  return price * quantity;
}

A framework such as Vitest or Jest could express tests conceptually as follows:

describe('totalPrice', () => {
  it('multiplies price by quantity', () => {
    expect(totalPrice(12, 3)).toBe(36);
  });

  it('rejects a negative quantity', () => {
    expect(() => totalPrice(12, -1)).toThrow();
  });
});

The tests specify both normal and invalid behaviour. A good test suite also considers zero, decimal prices, large values, missing values, and the precision expectations of the application. Financial applications should not assume that binary floating-point arithmetic exactly represents every decimal amount; they may use integer minor units or a decimal library.

4.3 Browser test example

A browser test might load a page, locate a button by its accessible role, click it, and assert that a success message appears. Prefer stable, user-oriented selectors such as labels, roles, and visible text. Selectors based on generated CSS classes are fragile.

Tests should not merely repeat implementation details. A test that asserts a private variable has a particular name provides little protection when the user-visible behaviour is unchanged. Conversely, a test for keyboard submission and an accessible error message protects an important requirement.

4.4 Test doubles and test scope

A mock or stub replaces a dependency so that a test can control conditions. For example, a network request can be replaced with a predictable response. Test doubles make unit tests fast, but excessive mocking can hide integration failures. Important boundaries—such as authentication, API contracts, and persistence—also require integration or end-to-end tests.

5. Validation, linting, and code review

Validation checks conformance to a formal language or standard. HTML validation can find unclosed elements, duplicate identifiers, invalid nesting, and obsolete attributes. CSS validation can identify malformed declarations and invalid properties. Validation does not prove that a page is usable or visually correct.

Linting applies programmable rules to source code. A JavaScript linter such as ESLint can detect undefined variables, unreachable code, suspicious equality, inconsistent style, and some error-prone patterns. A formatter such as Prettier addresses consistent presentation; it is complementary to, not a replacement for, a linter.

A sensible project pipeline is:

edit → format → lint → unit tests → build → browser tests → review → deploy

A linter rule should be understood rather than blindly disabled. If a rule is inappropriate, document a narrowly scoped exception. Code review should consider correctness, accessibility, security, performance, naming, error handling, and maintainability—not only style.

6. Performance foundations and optimisation

Performance is best treated as a measured property. Users experience loading, visual stability, and responsiveness, not abstract file size alone. Modern assessments commonly consider metrics such as Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), and Interaction to Next Paint (INP). Thresholds and measurement methods can change, so teams should consult current platform guidance and define project targets.

6.1 The critical rendering path

A simplified process is:

  1. the browser downloads HTML;
  2. it parses HTML into the DOM and CSS into the CSS Object Model;
  3. styles are combined to create a render tree;
  4. layout calculates geometry;
  5. paint and compositing produce pixels.

CSS can block rendering, and ordinary scripts can block HTML parsing. Use defer for scripts that should execute after parsing and preserve order. Use async only when scripts are independent of document parsing and of one another. Place critical metadata and styles appropriately, but do not move scripts without understanding their dependencies.

6.2 Asset and network practices

Useful practices include:

  • compress and appropriately size images;
  • use modern image formats when browser support and fallback requirements permit;
  • use responsive images with srcset and sizes;
  • lazy-load below-the-fold images when it improves, rather than harms, user experience;
  • minify production CSS and JavaScript;
  • remove unused code through bundler tree-shaking where safe;
  • cache versioned static assets with suitable headers;
  • avoid unnecessary third-party scripts;
  • preload only genuinely critical resources;
  • reduce duplicate requests and connection overhead.

Compression reduces transferred bytes but does not necessarily reduce decoding or execution cost. A small script can still be expensive if it performs heavy work on the main thread.

6.3 Runtime performance

Efficient interfaces batch DOM updates, avoid repeated forced layout, and use event delegation where many similar elements exist. Debouncing waits until rapid input settles; throttling limits how often a handler runs. These techniques should be applied according to the interaction requirement, not automatically.

Performance measurements must be reproducible. Record browser, device conditions, network profile, cache state, page version, and measurement method. Compare before and after using the same conditions. Optimising without measurement can make code more complex while producing no meaningful benefit.

7. Client-side security

The browser is an untrusted environment. Users can alter HTML, JavaScript, requests, and local storage. Client-side checks improve usability but cannot enforce permissions or protect secrets. Authorisation must be performed by a trusted server for protected operations.

7.1 Cross-site scripting (XSS)

XSS occurs when attacker-controlled data is interpreted as executable markup or script in another user’s browser. Common categories include:

  • Reflected XSS, in which malicious input is returned immediately in a response;
  • Stored XSS, in which malicious content is saved and later displayed;
  • DOM-based XSS, in which client-side code places unsafe data into a dangerous DOM sink.

The primary defence is context-appropriate output encoding and safe DOM APIs. Use textContent for plain text rather than innerHTML. Build elements with createElement, assign safe properties, and append them. If rich HTML is genuinely required, use a carefully configured, maintained sanitiser and a restrictive Content Security Policy (CSP). Avoid constructing JavaScript, CSS, or URLs by concatenating untrusted strings.

innerHTML is not automatically unsafe; it is unsafe when its value can contain untrusted markup that has not been appropriately sanitised. Trusted Types can provide an additional browser-enforced discipline in supported environments.

7.2 Injection and unsafe requests

Injection occurs when data is confused with instructions. In front-end code, risks include unsafe HTML, dynamic script construction, unsafe URL handling, and passing untrusted strings to application APIs. SQL injection is primarily a server-side concern, but a front end may still transmit dangerous input to a vulnerable server. Server-side parameterised queries and validation are essential.

Validate input for correctness and business rules, but do not rely on validation as the only XSS defence. Validation asks whether input is acceptable; encoding asks how it may safely be displayed in a particular context.

7.3 Credentials, storage, and transport

Never place API secrets, private keys, or long-lived privileged credentials in client-side JavaScript. Anything shipped to the browser can be inspected. Use HTTPS to protect data in transit. Authentication systems should use secure, appropriately configured cookies where suitable; cookie attributes such as Secure, HttpOnly, and SameSite reduce particular risks, although they do not solve every authentication problem.

localStorage is convenient but readable by JavaScript running on the origin. Storing a sensitive bearer token there increases the impact of XSS. Do not store passwords in browser storage. Clear sensitive data when no longer required and minimise collection.

7.4 Dependency and supply-chain risk

Third-party libraries can contain vulnerabilities, malicious code, excessive permissions, or licensing constraints. Reduce risk by:

  • selecting maintained dependencies with a credible release history;
  • pinning or locking versions where appropriate;
  • reviewing transitive dependencies;
  • running dependency audit tools;
  • updating promptly after assessing compatibility;
  • removing unused packages;
  • using Subresource Integrity for suitable externally hosted static resources;
  • applying CSP and monitoring unexpected network connections.

An audit report is a prioritisation aid, not proof that an application is secure. A vulnerable package may be unreachable in the deployed feature, while a newly disclosed issue may not yet appear in a tool.

7.5 Other essential controls

Use output encoding, CSRF defences where applicable, safe redirects, restrictive permissions, and careful error messages. Do not expose stack traces or internal configuration to users. Security headers such as CSP, frame restrictions, and appropriate MIME-type protection should be configured with the deployment environment. Accessibility and privacy are also ethical responsibilities: do not treat security as permission to exclude users or collect unnecessary personal data.

8. Illustrative case: improving a course-registration page

Background

A fictional college course-registration page allows a learner to search courses and add one to a list. Users report slow loading, occasional blank results, and a warning from a security scanner.

Problem analysis

The Network panel shows a large uncompressed banner image and several third-party scripts. The Console shows an exception when the search response is empty. Inspection reveals that results are inserted with innerHTML using the search term and server response. A dependency audit reports an outdated package.

Approach and solution

The team first writes tests for empty results, successful registration, duplicate courses, and invalid input. The search rendering function is changed to create list elements and assign course names with textContent. The server is required to validate registration permissions; the browser does not make that decision.

The image is resized and compressed, noncritical scripts are deferred or removed, and cacheable assets receive versioned filenames. The exception is replaced with an explicit empty-state message. The outdated package is upgraded after its tests pass. HTML and CSS validation, linting, a keyboard review, and a browser test are added to the build process.

Outcome

The page becomes smaller and more predictable, empty searches no longer fail, and untrusted course text is treated as text rather than markup. The team does not claim that client-side changes alone make registration secure; the server remains responsible for authorisation and data integrity.

Learning points

The same investigation can reveal performance, reliability, and security problems. Tests should be added before or alongside changes, and each improvement should be measured. Security controls must be placed at the correct trust boundary rather than delegated to the browser.

9. A practical quality checklist

Before release, confirm that:

  • the main user journeys have repeatable tests;
  • errors are reproducible and understandable;
  • HTML and CSS validate where applicable;
  • linting and formatting run consistently;
  • keyboard and accessibility checks have been performed;
  • network and performance measurements meet agreed targets;
  • images, scripts, and fonts are appropriately sized and loaded;
  • untrusted data is not inserted into unsafe DOM sinks;
  • server-side authentication, authorisation, and validation exist;
  • secrets are absent from client bundles;
  • dependencies are inventoried, reviewed, and updated;
  • production errors do not disclose sensitive information;
  • changes have been reviewed and regression-tested.

Chapter Summary

Professional front-end development includes more than writing markup, styles, and scripts. Browser developer tools help locate failures by exposing the DOM, styles, execution state, network activity, and performance timeline. A disciplined debugging process reproduces a problem, isolates its layer, tests a hypothesis, and verifies the correction.

Automated unit, integration, and browser tests provide repeatable evidence, while manual exploratory and accessibility testing address areas automation cannot fully judge. Validation checks conformance; linting detects suspicious or inconsistent code; neither proves that requirements are satisfied.

Performance work should be measured and should consider the critical rendering path, asset size, caching, script scheduling, and runtime work. Security requires treating browser input and code as untrusted, preventing unsafe DOM interpretation, avoiding exposed secrets, reviewing dependencies, and enforcing permissions on the server. A release workflow that combines tests, validation, performance evidence, security review, and human inspection produces safer and more maintainable applications.

Key Terms

Debugging: The systematic process of finding and correcting the cause of incorrect behaviour.

Developer tools: Browser tools for inspecting, executing, profiling, and testing web applications.

DOM: The in-memory tree representation of an HTML document.

Breakpoint: A location at which program execution pauses for inspection.

Unit test: A test of a small, isolated function or module.

Integration test: A test of cooperating components.

End-to-end test: A test of an application through a realistic user workflow.

Regression test: A test that checks previously working behaviour after a change.

Validation: Checking whether code conforms to a formal language or standard.

Linting: Static analysis that identifies suspicious, error-prone, or inconsistent code.

Critical rendering path: The sequence by which a browser obtains resources, computes styles and layout, and paints a page.

Lazy loading: Deferring a resource until it is likely to be needed.

XSS: Cross-site scripting, in which untrusted data is executed as browser content or script.

DOM sink: An API or operation, such as unsafe HTML insertion, where data may become interpreted markup or code.

Output encoding: Transforming data so it is safe in a particular output context.

CSP: Content Security Policy, a browser policy restricting permitted content sources and behaviours.

Dependency: An external package or library required by an application.

Authorisation: A trusted decision about what an authenticated actor may do.

Supply-chain risk: Security or integrity risk introduced through external software, services, or build components.

Review Questions

Short Answer Questions

  1. What is the difference between a defect and a failure?
  2. What information can the Network panel reveal?
  3. Why is textContent generally safer than assigning untrusted data to innerHTML?
  4. What is the purpose of a linter?
  5. Why can client-side validation not replace server-side authorisation?

Long Answer Questions

  1. Explain a systematic debugging workflow using the Console, Elements, Sources, and Network panels.
  2. Compare unit, integration, end-to-end, and manual exploratory testing, including an appropriate use for each.
  3. Explain how script loading, image optimisation, caching, and runtime JavaScript affect performance.
  4. Describe reflected, stored, and DOM-based XSS and give a defence for each.
  5. Design a release-quality checklist for a small HTML, CSS, and JavaScript application.

Analytical / Application Questions

  1. A page has a large image, three render-blocking scripts, repeated layout changes, and a slow API request. Propose an investigation order and justify each step.
  2. A search result is inserted using innerHTML because course names may contain formatting. Analyse the security risk and propose a safer design that still supports legitimate rich text if it is genuinely required.
  3. A dependency scanner reports a high-severity vulnerability in a package used only by a development tool. Explain the evidence you would gather before deciding whether to update, remove, or accept the risk.

Multiple Choice Questions with Answers

Question 1

Which developer-tools panel is most useful for finding whether a script or image is delaying page loading?

A. Elements

B. Network

C. Sources

D. Console

Correct Answer: B. Network

Question 2

Which practice best follows the Arrange–Act–Assert testing pattern?

A. Change production code, then format it

B. Arrange inputs, perform an action, and check the expected result

C. Inspect CSS before writing requirements

D. Run only an end-to-end test after deployment

Correct Answer: B. Arrange inputs, perform an action, and check the expected result

Question 3

What does HTML validation primarily check?

A. Whether users find the page attractive

B. Whether the server authorises requests

C. Whether markup conforms to relevant syntax and standards

D. Whether JavaScript executes quickly

Correct Answer: C. Whether markup conforms to relevant syntax and standards

Question 4

Which API is generally appropriate for inserting untrusted plain text into an element?

A. textContent

B. eval

C. innerHTML

D. document.write

Correct Answer: A. textContent

Question 5

What is the main limitation of client-side authorisation checks?

A. They always slow down rendering

B. Users can alter or bypass browser code

C. They prevent all accessibility testing

D. They require a CSS preprocessor

Correct Answer: B. Users can alter or bypass browser code

Question 6

Which attribute combination can help reduce cookie exposure to JavaScript and insecure transport?

A. HttpOnly and Secure

B. Public and Cacheable

C. Async and Defer

D. Readable and Visible

Correct Answer: A. HttpOnly and Secure

Question 7

What is a likely benefit of using defer for an ordinary page script?

A. It executes before HTML parsing begins

B. It prevents the script from ever executing

C. It allows parsing to continue and runs after document parsing

D. It converts the script into CSS

Correct Answer: C. It allows parsing to continue and runs after document parsing

Question 8

Which statement about dependency audits is most accurate?

A. A clean audit proves the application is secure

B. Audits are useless for transitive dependencies

C. An audit helps prioritise risks but requires contextual review

D. Audits remove vulnerabilities automatically

Correct Answer: C. An audit helps prioritise risks but requires contextual review

Question 9

Which test is most directly suited to checking that a pure price-calculation function returns the expected value?

A. Unit test

B. Visual inspection only

C. Network load test

D. Manual accessibility review

Correct Answer: A. Unit test

Question 10

Why should performance comparisons use consistent device and network conditions?

A. To make measurements reproducible

B. To ensure CSS is never validated

C. To prevent all caching

D. To replace security testing

Correct Answer: A. To make measurements reproducible

HTML, CSS & JavaScript
Chapter 13 of 15