Feedback-Formular für eine Website oder App

Feedback-Formular für eine Website oder App6 Min. Lesezeit

Feedback von Beta-Testern Build für Build sammeln

Ein Beta-Feedback-Formular mit einem persönlichen Link pro Tester und Build, zwei Bewertungsskalen und einer Wochenübersicht, die zeigt, ob jeder Build besser wurde.

Preet SutharGründer, Ikiform

Gib jedem Tester einen persönlichen Link mit einem undurchsichtigen Tester-Code und der Build-Nummer, damit das Formular weiß, wer und welcher Build, ohne zu fragen. Frag, wofür der Build genutzt wurde, zwei Bewertungen von 1 bis 5 für Stabilität und Nutzen und was die Person aufgehalten hat. Vergleiche dann Builds, nicht einzelne Antworten, um zu sehen, ob jedes Release die Zahlen bewegt hat.

Was an Beta-Feedback anders ist

Öffentliches Feedback kommt von Fremden, einmal. Beta-Feedback kommt von derselben kleinen Gruppe, immer wieder, zu Software, die sich jede Woche ändert. Eine einzelne Antwort zählt weniger als der Trend: Fühlte sich Build 1.4.0 für dieselben Leute stabiler an als 1.3.0? Dafür braucht es zwei Dinge, die ein normales Feedback-Formular nicht hat: wer geantwortet hat und auf welchem Build.

Tester ihre E-Mail und Versionsnummer eintippen zu lassen, scheitert in der Praxis. Sie vergessen die Version, vertippen sich oder schreiben „neueste“. Also kommt beides in den Link, den du verschickst, und die Seite füllt es aus.

Die wöchentliche Beta-Feedback-Schleife, von einem neuen Build zum nächstenBuild1.4.0erscheintandieBeta-GruppeEinladungs-E-MaileinLinkproTesterTesterantwortetCodeundBuildvorausgefülltÜbersichtproBuildDurchschnitte,Tester,VermissenBuild1.5.0behebtdiegrößtenBlocker?t=…&build=APIDie wöchentliche Beta-Feedback-Schleife, von einem neuen Build zum nächstenBuild 1.4.0 erscheintan die Beta-GruppeEinladungs-E-Mailein Link pro TesterTester antwortetCode und Build vorausgefülltÜbersicht pro BuildDurchschnitte, Tester,VermissenBuild 1.5.0behebt die größten Blocker?t=…&build=API
  1. Ein neuer Build geht an die Beta-Gruppe.
  2. Ein Skript erstellt pro Tester einen Link mit Code und Build-Nummer, und du verschickst sie per E-Mail.
  3. Der Tester öffnet den Link. Die Seite füllt die versteckten Felder für Tester und Build aus und zeigt, nach welchem Build sie fragt.
  4. Ein Übersichtsskript liest jede Antwort über die API und bildet die Durchschnitte pro Build.
  5. Die Blocker aus den niedrigsten Bewertungen werden zur Fix-Liste für den nächsten Build.
Vergleiche Spalten, nicht Zeilen. Die schlechte Woche eines Testers ist Rauschen; ein Rückgang in der ganzen Gruppe ist eine Regression.

Die Felder

Vorschau

Führt den Validator von Ikiform in deinem Browser aus. Es wird nichts gesendet.

Wie stabil hat er sich angefühlt?
Wie nützlich war er?
Wenn du ihn nicht mehr nutzen könntest, wie würdest du dich fühlen?
tester und build sind Hidden Inputs, die aus dem Link befüllt werden. Ihre Patterns lehnen alles ab, was nicht aus einer echten Einladung stammt.
FeldWarum
testerEin zufälliger Code aus 10 Zeichen. Damit verfolgst du eine Person über Builds hinweg, ohne ihre E-Mail in eine URL zu schreiben
buildVerknüpft jede Bewertung mit einem Release. Das Pattern akzeptiert nur Versionen wie 1.4.0
triedWas sie tatsächlich getan haben. Ohne das sind Bewertungen schwer zu deuten
stability, usefulnessZwei rating-Felder, 1 bis 5. Getrennt, denn ein stabiler Build des Falschen ist immer noch das Falsche
would_missWie enttäuscht sie ohne ihn wären. Ein wachsender Anteil von „Sehr“ ist das stärkste Zeichen, dass die Beta funktioniert
blockersOptional, und hier entsteht die Fix-Liste

Führe eine einfache Liste der Tester-E-Mails in testers.txt. Dieses Skript gibt jedem beim ersten Auftauchen einen festen Code, speichert die Zuordnung in testers.json und schreibt pro Tester einen Einladungslink für den Build, den du angibst. Beginne testers.json mit {}.

links.mjs
import { randomBytes } from "node:crypto";
import { readFileSync, writeFileSync } from "node:fs";

const BUILD = process.argv[2];
if (!/^\d+\.\d+\.\d+$/.test(BUILD ?? "")) throw new Error("Usage: node links.mjs 1.4.0");

const known = JSON.parse(readFileSync("testers.json", "utf8"));
const emails = readFileSync("testers.txt", "utf8").split(/\r?\n/).map((line) => line.trim().toLowerCase()).filter(Boolean);

for (const email of emails) {
  known[email] ??= randomBytes(5).toString("hex");
}
writeFileSync("testers.json", JSON.stringify(known, null, 2));

const rows = emails.map((email) => {
  const link = new URL("https://example.com/beta/feedback");
  link.searchParams.set("t", known[email]);
  link.searchParams.set("build", BUILD);
  return `${email},${link}`;
});
writeFileSync(`invites-${BUILD}.csv`, ["email,link", ...rows].join("\n"));
console.log(`${rows.length} links for build ${BUILD}`);

Verschick die CSV über die Serienbrief-Funktion deines E-Mail-Tools oder füge die Links bei einer kleinen Gruppe von Hand ein. Halte testers.json privat: Nur dort lässt sich ein Code einer Person zuordnen.

Die Feedback-Seite

Setze das auf eine Seite unter /beta/feedback deiner Site. Sie liest Code und Build aus dem Link, zeigt das Formular ohne sie nicht an und sendet auf dem normalen Weg. Setze Redirect after submit auf eine Dankeseite. Das funktioniert auf jedem statischen Host oder Baukasten, der ein Skript erlaubt.

beta/feedback.html
<p id="beta-missing" hidden>This page needs the link from your invite email.</p>

<form id="beta" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post" hidden>
  <h2>Feedback for build <span id="beta-build"></span></h2>
  <input type="hidden" name="tester" />
  <input type="hidden" name="build" />
  <label for="tried">What did you use this build for?</label>
  <textarea id="tried" name="tried" rows="4" required minlength="15"></textarea>
  <fieldset>
    <legend>How stable did it feel?</legend>
    <label><input type="radio" name="stability" value="1" required /> 1, it crashed a lot</label>
    <label><input type="radio" name="stability" value="2" /> 2</label>
    <label><input type="radio" name="stability" value="3" /> 3</label>
    <label><input type="radio" name="stability" value="4" /> 4</label>
    <label><input type="radio" name="stability" value="5" /> 5, no problems</label>
  </fieldset>
  <fieldset>
    <legend>How useful was it?</legend>
    <label><input type="radio" name="usefulness" value="1" required /> 1, not at all</label>
    <label><input type="radio" name="usefulness" value="2" /> 2</label>
    <label><input type="radio" name="usefulness" value="3" /> 3</label>
    <label><input type="radio" name="usefulness" value="4" /> 4</label>
    <label><input type="radio" name="usefulness" value="5" /> 5, I rely on it</label>
  </fieldset>
  <fieldset>
    <legend>If you could no longer use it, how would you feel?</legend>
    <label><input type="radio" name="would_miss" value="very" required /> Very disappointed</label>
    <label><input type="radio" name="would_miss" value="somewhat" /> Somewhat disappointed</label>
    <label><input type="radio" name="would_miss" value="not" /> Not disappointed</label>
  </fieldset>
  <label for="blockers">Anything that stopped you?</label>
  <textarea id="blockers" name="blockers" rows="3"></textarea>
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Send this week's feedback</button>
</form>

<script>
  const params = new URLSearchParams(location.search);
  const tester = params.get("t") ?? "";
  const build = params.get("build") ?? "";
  const form = document.getElementById("beta");
  if (/^[a-z0-9]{10}$/.test(tester) && /^\d+\.\d+\.\d+$/.test(build)) {
    form.elements.tester.value = tester;
    form.elements.build.value = build;
    document.getElementById("beta-build").textContent = build;
    form.hidden = false;
  } else {
    document.getElementById("beta-missing").hidden = false;
  }
</script>
Wer den Link eines Testers hat, kann als dieser Tester antworten. Für eine Beta ist das vertretbar: Die Codes sind zufällig, die Gruppe ist klein, und eine merkwürdige Antwort fällt auf. Nutze den Code nicht, um Zugriff auf irgendetwas zu gewähren.

Builds vergleichen

Das Dashboard zeigt einzelne Antworten. Um Builds zu vergleichen, lies sie alle mit dem SDK und gruppiere nach build. Führe das mit einem Secret Key in IKIFORM_SECRET_KEY aus:

summary.mjs
import { createClient } from "@ikiform/sdk";

const ikiform = createClient({ apiKey: process.env.IKIFORM_SECRET_KEY });
const builds = new Map();

for await (const submission of ikiform.submissions.iterate({ formId: "frm_xxxxxxxxxxxxxxxx", status: "received" })) {
  const { build, tester, stability, usefulness, would_miss } = submission.fields;
  const row = builds.get(build) ?? { testers: new Set(), stability: 0, usefulness: 0, very: 0, answers: 0 };
  row.testers.add(tester);
  row.stability += stability;
  row.usefulness += usefulness;
  row.very += would_miss === "very" ? 1 : 0;
  row.answers += 1;
  builds.set(build, row);
}

console.table(
  [...builds].map(([build, row]) => ({
    build,
    testers: row.testers.size,
    stability: (row.stability / row.answers).toFixed(1),
    usefulness: (row.usefulness / row.answers).toFixed(1),
    "very disappointed": `${Math.round((row.very / row.answers) * 100)}%`,
  })),
);

Bewertungen kommen als Zahlen an, also brauchen die Durchschnitte kein Parsing. Zähle neben jedem Durchschnitt die Tester: Ein Build mit drei Antworten lässt sich nicht fair mit einem mit dreißig vergleichen.

Prüfen, ob es funktioniert

  1. Führe node links.mjs 1.4.0 aus und öffne einen der Links. Das Formular zeigt „Feedback for build 1.4.0“.
  2. Öffne die Seite ohne Query-String. Nur der Hinweis auf die Einladung erscheint.
  3. Sende eine Antwort, ändere dann build in den Entwicklertools des Browsers auf latest und sende erneut. Ikiform antwortet mit 422 und „Use a version like 1.4.0“.
  4. Führe summary.mjs aus. Die Tabelle zeigt Build 1.4.0 mit einem Tester und deinen zwei Bewertungen.

Stolperfallen

  • Beta-Gruppen testen oft aus einem Büro, das sich eine IP teilt. Das Limit Standard zählt sie zusammen, also wähle für dieses Formular Relaxed, 20 pro Minute pro Besucher.
  • Mobile Betas über TestFlight lassen Tester bereits Screenshot-Feedback an App Store Connect senden. Nutze das für Bugs auf Bildschirmebene und dieses Formular für die wöchentlichen Fragen.
  • Stell jede Woche dieselben Fragen. Eine geänderte Formulierung macht den Vergleich zwischen Builds kaputt.
  • Tester antworten im Laufe der Beta seltener. Halte das Formular bei ein paar Minuten und danke den Leuten namentlich in den Release Notes.
  • Ikiform führt wiederholte Antworten nicht zusammen. Antwortet ein Tester für einen Build zweimal, behalte in deiner Übersicht die spätere.

Fehlerbehebung

ProblemWas zu tun ist
422 mit „Open the link from your invite email“Der Wert von tester passt nicht zum Pattern, meist weil der Link in einem E-Mail-Client abgeschnitten wurde. Schick den Link erneut.
429 rate_limitedEin Besucher hat mehr gesendet, als das Rate-Limit des Formulars erlaubt. Standard erlaubt 5 pro Minute und Besucher. Warte Retry-After ab oder wähle Relaxed oder Custom für Formulare, die von einer gemeinsamen IP aus senden.
403 origin_not_allowed, oder jeder Post landet im SpamDie Domain der Seite steht nicht unter Allowed domains des Formulars. Füge sie hinzu, plus localhost für lokale Tests. Subdomains einer eingetragenen Domain sind erlaubt.

Fragen

Hängst du bei einem Schritt fest oder willst eine Anleitung für deinen Fall? Schreib an hello@ikiform.com.