Resource library

QA How-To

Promises and error handling in tests: A QA Guide (2026)

Master promises and error handling in tests QA teams can trust, with async patterns, rejection assertions, cleanup, timeouts, diagnostics, and CI guidance.

20 min read | 3,177 words

TL;DR

Promises and error handling in tests are about making completion and failure visible to the test runner. Await every required operation, assert expected rejections explicitly, use concurrency intentionally, cancel slow work, and guarantee cleanup without swallowing the original error.

Key Takeaways

  • An asynchronous test is correct only when the runner can observe the promise that represents all required work.
  • Use assert.rejects or the runner's rejection matcher for expected failures, and verify meaningful error properties.
  • Choose sequential execution, Promise.all, Promise.allSettled, or Promise.race based on the behavior being tested.
  • Use finally or runner-managed teardown for cleanup, without replacing the primary test failure.
  • Add operation-level cancellation and timeouts so failures identify the slow boundary instead of hanging the suite.
  • Preserve the original error as a cause when adding domain context, and redact sensitive diagnostics.

Promises and error handling in tests qa engineers maintain must produce one trustworthy outcome: pass only after all required work succeeds, or fail with the most useful error when any required condition is not met. The central rule is simple. Return or await the promise that represents the complete test operation.

Async failures become difficult when code starts background work, catches too broadly, mixes completion styles, or lets cleanup hide the original exception. This guide turns those failure modes into concrete patterns using standard JavaScript and the stable Node.js test runner. The same reasoning applies to Playwright, Vitest, Jest, API clients, database helpers, and most promise-based automation libraries.

TL;DR

Situation Prefer Why
Steps depend on prior state sequential await Preserves required ordering
Independent required checks Promise.all Fails the combined operation when one rejects
Collect every cleanup result Promise.allSettled Reports each fulfillment and rejection
Expected async failure assert.rejects or a rejection matcher Proves the promise rejected for the right reason
Guaranteed local cleanup try...finally Runs cleanup after success or failure
Slow cancellable operation AbortSignal plus an operation timeout Stops work and reports the affected boundary

Do not add catch merely to print an error. If the test cannot recover or make a better assertion, let the rejection reach the runner.

1. Promises and error handling in tests QA fundamentals

A promise is an object representing an asynchronous result. It starts pending and settles once, either fulfilled with a value or rejected with a reason. Calling then produces another promise, and throwing inside a then callback rejects that derived promise. An async function always returns a promise. A returned value becomes fulfillment, and a thrown error becomes rejection.

A test runner needs to know which promise represents the test. In an async test callback, the returned promise is created automatically. Every awaited operation contributes to that promise's result. If one awaited call rejects and nothing handles it, the test promise rejects and the runner records a failure.

import test from 'node:test';
import assert from 'node:assert/strict';

async function lookupUser(id) {
  if (id === 'missing') {
    throw new Error('User not found');
  }
  return { id, active: true };
}

test('returns an active user', async () => {
  const user = await lookupUser('u-17');
  assert.equal(user.active, true);
});

This is desirable propagation. The test does not need a try...catch around lookupUser unless it expects the rejection or can add useful context.

Remember that promise reactions run after the current synchronous stack. That timing explains why a floating promise can outlive its test callback. It also explains why wrapping a promise-returning call with a synchronous assertion such as assert.throws is wrong. The function returns normally with a promise, then that promise rejects later. Use an asynchronous rejection assertion.

A solid Node.js guide for QA engineers provides the runtime background behind these rules. Here, the focus is the test's observable completion contract.

2. Make every asynchronous test observable

A false pass often looks reasonable at first glance:

test('bad: assertion is detached from the test', () => {
  lookupUser('u-17').then((user) => {
    assert.equal(user.active, false);
  });
});

The callback passed to test returns undefined immediately. The runner can finish the case before the then callback throws its assertion error. The fixed version is either async with await or a direct returned promise:

test('good: await the operation', async () => {
  const user = await lookupUser('u-17');
  assert.equal(user.active, true);
});

test('also good: return the complete chain', () => {
  return lookupUser('u-17').then((user) => {
    assert.equal(user.active, true);
  });
});

Prefer async and await for multi-step tests because control flow and stack traces are easier to read. Returning a chain remains valid and is useful when teaching how promises compose.

The rule extends into helpers. A helper that starts an assertion but does not return its promise detaches work even when the test awaits the helper:

function badCheckStatus(page) {
  page.getByRole('status').textContent().then((text) => {
    assert.equal(text, 'Ready');
  });
}

async function checkStatus(page) {
  const text = await page.getByRole('status').textContent();
  assert.equal(text, 'Ready');
}

The corrected helper returns an async promise, and awaiting it covers the locator read and assertion.

Static analysis can catch many floating promises in TypeScript projects. Runtime discipline is still required for intentionally started background work. If a test launches a server, consumer, or observer, store its handle, define readiness, collect its eventual failure, and stop it during teardown. Labeling a promise "fire and forget" does not remove ownership.

3. Assert rejected promises precisely

Negative tests should prove that an operation fails for the intended reason. A test that passes for any rejection can hide a DNS failure, authentication problem, or programming bug instead of validating the business rule.

Node's strict assertion module provides assert.rejects:

import test from 'node:test';
import assert from 'node:assert/strict';

class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

async function registerUser(input) {
  if (!input.email.includes('@')) {
    throw new ValidationError('email', 'Email format is invalid');
  }
  return { id: 'new-user', ...input };
}

test('rejects an invalid email', async () => {
  await assert.rejects(
    () => registerUser({ email: 'invalid' }),
    (error) => {
      assert.equal(error.name, 'ValidationError');
      assert.equal(error.field, 'email');
      assert.match(error.message, /format is invalid/);
      return true;
    }
  );
});

The validator callback must return truthy after its assertions. You can also pass a regular expression or error-shape object where supported. The callback form is useful for domain properties.

A dangerous manual pattern is:

try {
  await registerUser({ email: 'invalid' });
} catch (error) {
  assert.match(error.message, /invalid/);
}

If registerUser unexpectedly fulfills, control skips the catch block and the test passes. If a manual catch is necessary, add an explicit failure after the awaited call, or use the runner matcher designed for rejection.

Do not overfit to the entire message if wording is not part of the public contract. Prefer error class, stable code, field name, HTTP status, or a targeted message fragment. Conversely, checking only "an error happened" is too weak when multiple failure paths exist. The negative test should distinguish the intended product response from infrastructure noise.

4. Error taxonomy for actionable test failures

Not every failure means the same thing. A useful automation system classifies errors enough to guide ownership without masking the original facts.

Error class Example Typical test response
Assertion failure expected status 201, received 409 Report observed and expected behavior
Product contract error response schema lacks orderId Fail at the API boundary with path context
Test data error required account was not created Report setup operation and unique data key
Infrastructure error connection refused Preserve endpoint, attempt, and cause
Timeout or cancellation response exceeded operation limit Name the timed operation and limit
Framework or code error undefined helper dependency Fail immediately, do not relabel as product defect

Custom errors are valuable when they add stable structured data:

export class HttpError extends Error {
  constructor(message, options) {
    super(message, { cause: options.cause });
    this.name = 'HttpError';
    this.status = options.status;
    this.url = options.url;
    this.responseText = options.responseText;
  }
}

Avoid building a deep hierarchy for every possible status. Often one HttpError with properties is enough. The test can assert status while a reporter safely renders a truncated, redacted body.

JavaScript allows any value to be thrown or used as a rejection reason, but production-quality test utilities should throw Error instances. Errors carry stacks and can preserve causes. When handling unknown caught values in TypeScript, narrow them before reading message.

Do not convert all problems into a boolean. A helper that returns false for timeout, authorization failure, malformed JSON, and a genuine negative result removes the distinction the test needs. Return a typed result only when failure is an expected domain outcome. Throw an error for an operation that could not produce a valid result.

Good classification improves triage, but the test report must still contain the original exception. A label such as "environment issue" without evidence creates a blind spot.

5. Use try, catch, and finally with clear intent

A catch block is appropriate when the test expects an error, can recover, can translate it at a layer boundary, or can attach meaningful context. It is not required around every await. Unhandled within the test callback is exactly how unexpected failures reach the runner.

This wrapper preserves the original cause:

async function createOrder(client, payload) {
  try {
    return await client.post('/orders', payload);
  } catch (error) {
    throw new Error(
      'Could not create order for reference ' + payload.reference,
      { cause: error }
    );
  }
}

The added reference helps triage, while cause retains the lower-level stack or HTTP error. Ensure the reference is safe to log.

Use finally for cleanup that must run whether the operation fulfills or rejects:

test('releases a reserved account', async () => {
  const account = await reserveAccount();
  try {
    const result = await runCheckout(account);
    assert.equal(result.status, 'confirmed');
  } finally {
    await releaseAccount(account.id);
  }
});

There is a subtle risk: if runCheckout fails and releaseAccount also fails, the cleanup rejection can replace the primary error. Runner-managed teardown may report both more clearly. If you use local finally, decide how to retain both outcomes. One option is to capture the primary error and attach cleanup context, but do not create complicated control flow unless double failure is realistic.

Never write catch (error) { console.log(error) } and then continue. That turns an unexpected rejection into a pass. Re-throw after adding context, or omit the catch. Also avoid returning from finally, because a return can override a thrown error or earlier returned value.

The API negative testing and error handling guide extends these principles to HTTP status families and error response contracts.

6. Choose promise concurrency based on the test oracle

Promises do not become concurrent because of Promise.all alone. The operations start when their functions are called. Promise.all collects their promises and fulfills with ordered results when all fulfill, or rejects when one rejects.

const [profile, permissions] = await Promise.all([
  client.getProfile(userId),
  client.getPermissions(userId)
]);

assert.equal(profile.id, userId);
assert.ok(permissions.includes('orders:read'));

This is appropriate if the calls are independent and both are required. Do not use it if the second depends on a token or record created by the first.

Promise.allSettled always fulfills with status objects after every input settles:

const results = await Promise.allSettled([
  deleteOrder(orderId),
  deleteCustomer(customerId),
  closeSession(sessionId)
]);

const failures = results.filter((result) => result.status === 'rejected');
assert.equal(
  failures.length,
  0,
  'Cleanup failures: ' + failures.map((item) => String(item.reason)).join('; ')
);

This fits best-effort cleanup or a diagnostic matrix. If you forget to inspect rejected entries, the test passes even when every operation fails.

Promise.race settles with the first settled input. It is sometimes used for timeouts, but a rejected timeout promise does not automatically cancel the losing operation. That work may continue and leak into later tests. Prefer APIs that accept an AbortSignal, and abort the operation when the deadline expires.

Promise.any fulfills with the first fulfillment and rejects with AggregateError only if every input rejects. It can validate redundant read endpoints, but it is rarely the right oracle if every endpoint is expected to work.

Choose the combinator by the requirement, not by a desire to make tests fast. Concurrency changes load, ordering, state collisions, and the shape of failures.

7. Timeouts, cancellation, and polling

A timeout should identify the operation that exceeded its budget. A single enormous suite timeout says only that something did not finish. Place boundaries around network calls, readiness waits, queue consumption, and other external operations.

For APIs that accept AbortSignal, create a timeout signal:

export async function fetchStatus(url, timeoutMs = 3000) {
  const response = await fetch(url, {
    signal: AbortSignal.timeout(timeoutMs),
    headers: { accept: 'application/json' }
  });

  if (!response.ok) {
    throw new Error('Status request failed with HTTP ' + response.status);
  }
  return response.json();
}

An abort produces a rejection. Tests can let it fail normally or assert it when cancellation is the behavior under test. Do not label every abort as a product timeout without checking who initiated it.

Polling should have a deadline, an interval, and a last-observed-state diagnostic:

import { setTimeout as delay } from 'node:timers/promises';

export async function waitForOrder(readOrder, id, options = {}) {
  const timeoutMs = options.timeoutMs ?? 5000;
  const intervalMs = options.intervalMs ?? 100;
  const deadline = Date.now() + timeoutMs;
  let lastStatus = 'not read';

  while (Date.now() < deadline) {
    const order = await readOrder(id);
    lastStatus = order.status;
    if (order.status === 'confirmed') {
      return order;
    }
    await delay(intervalMs);
  }

  throw new Error(
    'Order ' + id + ' was not confirmed within ' + timeoutMs +
    ' ms. Last status: ' + lastStatus
  );
}

This is deterministic enough for an integration boundary and gives a useful final observation. In UI automation, use the framework's retrying assertions instead of hand-written sleeps and polling.

Avoid await new Promise(resolve => setTimeout(resolve, 5000)) as synchronization. A fixed sleep is sometimes useful in an isolated timing test, but it is not evidence that an application state is ready.

8. Cleanup and failure aggregation

Cleanup is part of test correctness. Leaked users, messages, files, browser contexts, and servers make later tests unreliable. The owner of a resource should register cleanup immediately after creation, before risky assertions begin.

Most runners provide hooks or per-test teardown. Node's test context supports t.after:

test('creates and cancels an order', async (t) => {
  const order = await api.createOrder({ sku: 'BOOK-1', quantity: 1 });

  t.after(async () => {
    await api.deleteOrder(order.id);
  });

  const cancelled = await api.cancelOrder(order.id);
  assert.equal(cancelled.status, 'cancelled');
});

Registering cleanup directly after creation prevents a later assertion from skipping it. Make cleanup idempotent when practical. Deleting an already deleted record can be considered success if the postcondition is absence.

Parallel cleanup should use Promise.allSettled when you want every attempt to run. Report each failed resource identifier without exposing sensitive data. Do not let one rejected deletion prevent unrelated resources from being released.

There are cases where cleanup failure should fail an otherwise passing test. A leaked shared tenant can corrupt the suite, so hiding the failure is unsafe. When both the main operation and cleanup fail, preserve the primary assertion and attach cleanup diagnostics if the runner does not already show both.

Global cleanup is a last resort for resources not attributable to one test. Broad "delete all test records" logic can remove another worker's data or hide ownership defects. Tag resources with a run identifier and delete only what the run created.

Finally, stop background promises. An event consumer should expose a readiness promise and a stop method whose completion can be awaited. Removing a listener without closing its socket may still leave the process alive.

9. Framework-specific rejection patterns

The concepts remain constant while syntax changes. With Playwright Test, use its asynchronous matcher for promise rejections where appropriate:

import { test, expect } from '@playwright/test';

test('rejects an invalid API operation', async ({ request }) => {
  const createInvalidOrder = async () => {
    const response = await request.post('/api/orders', {
      data: { quantity: -1 }
    });

    if (response.ok()) {
      throw new Error('Expected API to reject a negative quantity');
    }
    throw new Error('Order rejected with HTTP ' + response.status());
  };

  await expect(createInvalidOrder()).rejects.toThrow(/HTTP 400/);
});

For an HTTP negative test, it is often clearer to assert the response directly rather than convert it into an exception:

test('returns a validation response', async ({ request }) => {
  const response = await request.post('/api/orders', {
    data: { quantity: -1 }
  });

  expect(response.status()).toBe(400);
  await expect(response.json()).resolves.toMatchObject({
    code: 'INVALID_QUANTITY'
  });
});

Choose based on the client contract. Playwright's request fixture returns an APIResponse for HTTP error statuses, so a 400 does not inherently mean the request promise rejects. Network failures and client wrappers may reject. Tests must know which layer owns status interpretation.

Vitest supports await expect(promise).rejects... and await expect(promise).resolves.... Always await those matcher chains. In any runner, confirm how unhandled rejections are reported and keep the runtime version consistent in CI.

For Playwright-specific types and lifecycle, read typing Playwright fixtures for QA. Typed fixtures can make resource ownership and dependency order explicit.

10. Diagnostics without swallowing or leaking data

A useful asynchronous failure answers: which operation failed, against which safe identifier, after what observation, and because of which underlying error? It should not dump access tokens, cookies, passwords, or entire customer records.

Add context at boundaries:

function redactHeaders(headers) {
  const copy = { ...headers };
  for (const key of Object.keys(copy)) {
    if (['authorization', 'cookie', 'set-cookie'].includes(key.toLowerCase())) {
      copy[key] = '[REDACTED]';
    }
  }
  return copy;
}

async function sendRequest(client, request) {
  try {
    return await client.send(request);
  } catch (error) {
    const context = {
      method: request.method,
      url: request.url,
      headers: redactHeaders(request.headers)
    };
    throw new Error('Request failed: ' + JSON.stringify(context), {
      cause: error
    });
  }
}

Log once at the reporting boundary when possible. Logging the same exception in a client, helper, fixture, and test produces noise and can expose data multiple times. Throw structured errors through the layers, then let the runner or reporter render them.

Keep assertion messages descriptive but accurate. "Checkout service is down" is an unsupported conclusion if all you observed was a locator timeout. "Confirmation heading was not visible within the assertion timeout" states the evidence.

Capture asynchronous correlation identifiers from response headers or payloads. They often connect a test failure to service logs. Include an application build, environment name, worker ID, and test data key when those values help reproduce the case.

Stack traces are most useful when errors remain errors. Throwing a string discards standard stack behavior. Wrapping without a cause hides the original call site. Catching and returning null moves the eventual failure far from its source. Each shortcut trades a few lines now for slower triage later.

11. Promises and error handling in tests QA review strategy

Review asynchronous tests by tracing ownership and completion. Start at the test callback and identify every promise created. Each must be awaited, returned, combined into an awaited promise, registered as supervised background work, or intentionally ignored with a documented reason and error sink.

Then trace failure behavior:

  1. What rejects if setup cannot create data?
  2. Does an expected rejection matcher fail when the operation unexpectedly fulfills?
  3. If two concurrent operations fail, which evidence remains?
  4. Can the timeout cancel the underlying work?
  5. Does cleanup run after an assertion failure?
  6. Can cleanup replace the primary error?
  7. Are unknown errors narrowed safely in TypeScript?
  8. Could diagnostic output expose secrets?
  9. Does the test runner receive a nonzero outcome in CI?
  10. Can another parallel test observe the same mutable state?

A good review also challenges unnecessary catches and retries. Ask what recovery occurs. If the answer is "we log it," remove the catch and let the runner report the rejection. If the answer is "we retry," verify the operation is safe to repeat and that each attempt has a bounded timeout.

Test the test when risk is high. Temporarily make the dependency reject, delay beyond the deadline, return an unexpected success, and fail cleanup. Confirm that each mutation creates a clear red result. This small fault-injection exercise reveals false-pass paths more reliably than reading syntax alone.

The final goal is not zero exceptions. It is failures that occur at the right boundary, carry the right evidence, and never masquerade as success.

Interview Questions and Answers

Q: What happens when an async function throws an error?

The function returns a rejected promise whose reason is that error. A caller can handle it with await inside try...catch, attach a rejection handler, or let it propagate. In a correctly written async test, the rejection reaches the runner and fails the case.

Q: Why can a test pass when a promise later rejects?

The test callback may have returned before the promise was connected to the runner. This commonly happens when code calls then without returning the chain or invokes an async helper without awaiting it. The later rejection is unhandled or reported outside the intended test.

Q: What is the difference between assert.throws and assert.rejects?

assert.throws checks a synchronous function call that throws before returning. assert.rejects awaits a promise or promise-returning function and checks its rejection. Using throws for an async function usually tests only that the function returned a promise, not the later failure.

Q: When is Promise.all unsafe in a test?

It is unsafe when operations depend on one another, mutate shared state without coordination, or create a load pattern the requirement does not allow. It also rejects on the first observed rejection while other started operations can continue. Use it only for genuinely independent required work and still own cleanup.

Q: How do you avoid losing the original error when adding context?

Create a new Error with a specific boundary message and pass the original error through the cause option. Preserve safe structured properties such as status and resource ID. Do not replace an exception with a string, null, or generic boolean.

Q: How should cleanup failures be handled?

Cleanup must run after both success and failure, preferably through runner-managed teardown or a careful finally. If cleanup fails, report the affected resource. When the test already failed, retain the primary failure and attach the cleanup error instead of silently replacing either result.

Q: What is an unhandled rejection?

It is a promise rejection with no rejection handler attached in the relevant turn of execution. In tests it often indicates floating work or a helper that lost its returned promise. Treat it as a suite defect even if the current runner happens to print only a warning.

Q: How would you test a timeout implementation?

Use a controlled fake operation that waits for an abort signal or a known delay, set a short test-specific timeout, and assert the rejection type or stable error property. Also verify that the underlying operation stops and does not produce work after the test completes.

Common Mistakes

  • Omitting await before an async operation or rejection matcher.
  • Using assert.throws for a function that returns a rejected promise.
  • Catching an error only to log it, then allowing the test to pass.
  • Writing a manual negative test that has no failure path when the operation fulfills.
  • Running state-dependent operations concurrently with Promise.all.
  • Using Promise.allSettled without checking rejected results.
  • Racing against a timeout promise without canceling the losing operation.
  • Returning from finally and overriding an earlier failure.
  • Letting cleanup errors erase the primary assertion failure.
  • Throwing strings or wrapping errors without preserving the cause.
  • Printing credentials or full response bodies in failure diagnostics.
  • Treating HTTP 4xx or 5xx responses as promise rejections when the client returns normal response objects.

The fix is to model completion explicitly. Await the whole operation, assert the correct failure contract, keep cleanup owned, and make diagnostics describe observations rather than assumptions.

Conclusion

Promises and error handling in tests qa teams can trust are built on observable completion. Await required work, use precise rejection assertions, select concurrency by the test requirement, and make timeouts cancel the operation they bound.

Take one flaky async test from your suite and trace every promise from creation to settlement. Add a deliberate rejection, an unexpected fulfillment, and a cleanup failure. If all three produce clear, correctly attributed results, the test has a sound error contract.

Interview Questions and Answers

How does a rejected promise fail an async test?

An async test callback returns a promise to the runner. If an awaited operation rejects and the test does not handle it, the callback's promise rejects as well. The runner records that rejection as the test failure.

What is a floating promise?

A floating promise is created without being awaited, returned, or deliberately supervised. Its work can outlive the intended test boundary, and its rejection may become unhandled. Linters help detect it, but resource ownership still requires design.

Compare Promise.all and Promise.allSettled for testing.

Promise.all is for independent operations that are all required, and it rejects when an input rejects. Promise.allSettled waits for every input and returns a status for each, which is useful for cleanup and diagnostics. With allSettled, the test must explicitly fail on rejected results when failure matters.

Why is assert.throws wrong for async failures?

An async function normally returns a promise synchronously, then that promise rejects later. assert.throws only observes a synchronous throw during the call. assert.rejects or an awaited rejection matcher observes the promise settlement.

When should you catch an error in a test helper?

Catch when the helper can recover, translate a boundary-specific contract, or attach meaningful safe context. Re-throw with the original error as cause. Do not catch merely to log or return false.

How do you design operation-level timeouts?

Give the external operation a defined budget and use its cancellation mechanism, commonly an AbortSignal. On expiry, stop the underlying work and report the operation name, duration, and last safe observation. Keep a suite timeout as a final containment layer.

How do you preserve both test and teardown failures?

Prefer runner-managed teardown when it reports lifecycle errors separately. Otherwise capture the primary error, run all cleanup with explicit result collection, and attach cleanup diagnostics without replacing the primary stack. Never silently discard cleanup failures that can contaminate later tests.

What should a negative async test assert?

It should prove that the operation rejects and that the rejection matches the intended contract. Stable error class, domain code, field name, HTTP status, or a focused message pattern are stronger than accepting any error. The test must also fail if the operation unexpectedly fulfills.

Frequently Asked Questions

Should every await in a test have a try-catch block?

No. Unexpected rejections should normally propagate to the test runner. Add try-catch only when the test expects the error, can recover, or can add useful context while preserving and rethrowing the original cause.

Why must I await expect(...).rejects in a test?

The rejection matcher itself is asynchronous and returns a promise. Without await or return, the test can finish before the matcher evaluates, creating a false pass or an unhandled failure.

Does Promise.all cancel other promises after one rejects?

No. Promise.all rejects when an input rejects, but the other operations that already started generally keep running. Use cancellation supported by the underlying APIs and ensure cleanup owns any resources they create.

When should tests use Promise.allSettled?

Use it when every operation must be attempted and every outcome collected, especially for independent cleanup or diagnostics. Inspect the returned status objects, because allSettled fulfills even when inputs reject.

How do I test that an async function throws?

Technically the async function returns a rejected promise, so use assert.rejects or your runner's rejects matcher. Assert stable properties such as the error class, code, field, status, or a targeted message fragment.

Can a finally block hide a test failure?

Yes. A return or thrown cleanup error in finally can override the earlier result. Avoid returning from finally, and use runner-managed teardown or explicit aggregation when both primary and cleanup failures must be retained.

What causes unhandled rejections in test automation?

Common causes are unawaited helpers, detached then chains, background observers without error handling, and cleanup started after a test ends. Trace each created promise to an owner and an observed settlement.

Related Guides