Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,13 @@ module.exports = {
label: "1.0.0",
path: "1.0.0",
banner: "unmaintained",
noIndex: true,
},
"2.0.0": {
label: "2.0.0",
path: "2.0.0",
banner: "unmaintained",
noIndex: true,
},
},
onlyIncludeVersions: ["1.0.0", "2.0.0", "4.0.0"],
Expand Down Expand Up @@ -464,6 +466,27 @@ module.exports = {
// 0.5 → /docs/concepts/reference/glossary/* (long-tail
// glossary; noindexed legacy versions excluded via
// netlify headers + robots.txt)
//
// Also exclude auto-generated tag indexes and the unmaintained
// 1.0.0 / 2.0.0 doc versions from the sitemap. Those versions
// additionally carry `noIndex: true` via their `versions` config
// above; excluding from the sitemap signals that they should not
// be ranked at all.
//
// Docusaurus matches `ignorePatterns` against the full route path
// including `baseUrl` (`/docs/`), so the patterns must carry that
// prefix — bare `/tags/**` and `/1.0.0/**` would never match the
// emitted `/docs/tags/...` and `/docs/1.0.0/...` routes. Bare
// patterns are kept as defence-in-depth in case `baseUrl` is ever
// flattened to `/`.
ignorePatterns: [
"/docs/tags/**",
"/docs/1.0.0/**",
"/docs/2.0.0/**",
"/tags/**",
"/1.0.0/**",
"/2.0.0/**",
Comment thread
slayerjain marked this conversation as resolved.
],
createSitemapItems: async (params) => {
const {defaultCreateSitemapItems, ...rest} = params;
const items = await defaultCreateSitemapItems(rest);
Expand Down
132 changes: 132 additions & 0 deletions src/components/HowTo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React from "react";
import Head from "@docusaurus/Head";

/**
* HowTo schema.org wrapper for Docusaurus MDX pages.
*
* Emits valid schema.org/HowTo JSON-LD into <head> and (optionally) renders a
* matching numbered <ol> of visible steps. Authors can pass `visible={false}`
* when the prose below already renders the steps so the JSON-LD is the only
* change to the page.
*
* Required HowTo fields per Google: name, step (array of HowToStep with name + text).
* Optional: totalTime (ISO 8601 duration), estimatedCost (MonetaryAmount), tool, supply.
*
* Example:
* <HowTo
* name="Install Keploy on Linux"
* totalTime="PT5M"
* estimatedCost={{currency: "USD", value: "0"}}
* tools={["bash", "curl"]}
* supplies={["Linux machine with kernel >= 5.10"]}
* steps={[
* {name: "Download", text: "Run: curl ...", url: "#download"},
* {name: "Install", text: "Run: sudo install ...", url: "#install"},
* ]}
* visible={false}
* />
*/
export default function HowTo({
name,
description,
totalTime,
estimatedCost,
tools,
supplies,
image,
steps,
visible = true,
}) {
if (!name || !Array.isArray(steps) || steps.length === 0) {
// Component is a no-op without the minimum required fields.
return null;
}

// Filter to steps that carry both `name` and `text` per Google's HowTo
// requirements. Auto-generating "Step N" placeholders or emitting empty
// `text` produces low-quality structured data that the rich-results test
// flags. If the author gave us nothing usable, drop the schema entirely
// rather than ship a hollow HowTo.
const validSteps = steps.filter(
(s) => typeof s.name === "string" && s.name.trim() &&
typeof s.text === "string" && s.text.trim(),
);
if (validSteps.length === 0) {
return null;
}

const schema = {
"@context": "https://schema.org",
"@type": "HowTo",
name,
step: validSteps.map((s, i) => {
const step = {
"@type": "HowToStep",
position: i + 1,
name: s.name,
text: s.text,
};
if (s.url) step.url = s.url;
if (s.image) step.image = s.image;
return step;
}),
};
Comment thread
slayerjain marked this conversation as resolved.

if (description) schema.description = description;
if (totalTime) schema.totalTime = totalTime;
if (image) schema.image = image;
if (estimatedCost && estimatedCost.value !== undefined) {
schema.estimatedCost = {
"@type": "MonetaryAmount",
currency: estimatedCost.currency || "USD",
value: String(estimatedCost.value),
};
}
if (Array.isArray(tools) && tools.length > 0) {
schema.tool = tools.map((t) =>
typeof t === "string" ? {"@type": "HowToTool", name: t} : t,
);
}
if (Array.isArray(supplies) && supplies.length > 0) {
schema.supply = supplies.map((s) =>
typeof s === "string" ? {"@type": "HowToSupply", name: s} : s,
);
}

return (
<>
<Head>
<script type="application/ld+json">{JSON.stringify(schema)}</script>
</Head>
{visible && (
<section
aria-label={name}
style={{
border: "1px solid var(--ifm-color-emphasis-200)",
borderRadius: "12px",
padding: "1rem 1.25rem",
margin: "1rem 0 1.5rem",
background: "var(--ifm-background-surface-color)",
}}
>
<h3 style={{marginTop: 0}}>{name}</h3>
{description && <p>{description}</p>}
<ol>
{/* Don't derive an `id` from `s.url`. In docs usage `step.url`
often points at an existing heading anchor on the page (e.g.
`#capturing-testcases`), so reusing that as a list-item id
would produce duplicate ids in the DOM whenever `visible`
is enabled. The list is the readable view; `step.url` in
the JSON-LD already covers the schema linkage. */}
{validSteps.map((s, i) => (
<li key={i}>
<strong>{s.name}</strong>
<div>{s.text}</div>
</li>
))}
</ol>
</section>
)}
</>
);
}
48 changes: 47 additions & 1 deletion src/pages/about.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
import React from "react";
import Layout from "@theme/Layout";
import Head from "@docusaurus/Head";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import useBaseUrl from "@docusaurus/useBaseUrl";

// Custom React pages under src/pages/ are not covered by the docs schema
// plugin — add Article + BreadcrumbList JSON-LD inline so the page is
// machine-readable for search engines and AI crawlers.
//
// Site config sets `trailingSlash: true`, so canonical URLs in the JSON-LD
// must carry the trailing slash to match the actual emitted href and avoid
// duplicate URL variants in structured data.
const aboutStructuredData = [
{
"@context": "https://schema.org",
"@type": "Article",
headline: "About the Keploy Documentation",
description:
"Information about Keploy's documentation, contribution guidelines, and licensing.",
url: "https://keploy.io/docs/about/",
publisher: {
Comment thread
slayerjain marked this conversation as resolved.
"@type": "Organization",
name: "Keploy",
logo: {
"@type": "ImageObject",
url: "https://keploy.io/docs/img/favicon.png",
},
},
mainEntityOfPage: {
"@type": "WebPage",
"@id": "https://keploy.io/docs/about/",
},
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{"@type": "ListItem", position: 1, name: "Home", item: "https://keploy.io/"},
{"@type": "ListItem", position: 2, name: "Docs", item: "https://keploy.io/docs/"},
{"@type": "ListItem", position: 3, name: "About", item: "https://keploy.io/docs/about/"},
],
},
];

function About() {
const context = useDocusaurusContext();
Expand All @@ -12,6 +51,13 @@ function About() {
permalink="/about"
description="User General Information about Keploy's Documentation"
>
<Head>
{aboutStructuredData.map((schema, i) => (
<script key={i} type="application/ld+json">
{JSON.stringify(schema)}
</script>
))}
</Head>
<main className="margin-vert--lg container">
<h1>About the docs</h1>
<div className="margin-bottom--lg">
Expand Down
66 changes: 64 additions & 2 deletions src/pages/concepts/reference/glossary.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,65 @@
import React, {useState, useMemo} from "react";
import Layout from "@theme/Layout";
import Head from "@docusaurus/Head";
import BackToTopButton from "@theme/BackToTopButton";

import {glossaryEntries} from "../../../../static/data/glossaryEntries";
import GlossaryCard from "../../../components/GlossaryCard";

// SEO/GEO: turn each glossary entry into a DefinedTerm inside a single
// DefinedTermSet so AI engines can cite individual definitions and engines
// can surface them as featured-snippet definitions. Mirrors the pattern in
// landing/app/(default)/what-is-api-testing/layout.tsx.
//
// Site config sets `trailingSlash: true`, so every emitted URL must carry a
// trailing slash to match the canonical href. Otherwise Google treats the
// no-slash variant as a duplicate URL of the canonical one.
const allGlossaryItems = Object.values(glossaryEntries).flat();
const SITE = "https://keploy.io";
const GLOSSARY_PATH = "/docs/concepts/reference/glossary/";
const GLOSSARY_URL = `${SITE}${GLOSSARY_PATH}`;
const TERMSET_ID = `${GLOSSARY_URL}#termset`;

function withTrailingSlash(path) {
if (!path) return path;
return path.endsWith("/") ? path : `${path}/`;
}

const glossaryStructuredData = [
{
"@context": "https://schema.org",
"@type": "DefinedTermSet",
"@id": TERMSET_ID,
name: "Keploy Software Testing Glossary",
description:
"Definitions for software testing, test automation, and quality engineering terminology, maintained by the Keploy documentation team.",
url: GLOSSARY_URL,
// Defensive: an entry without a valid `link` (e.g. a typoed key like
// `ink:`) would emit `https://keploy.ioundefined` into the JSON-LD.
// Drop those entries here so structured data never carries a malformed
// URL even if `glossaryEntries` has gaps.
hasDefinedTerm: allGlossaryItems
.filter((entry) => typeof entry.link === "string" && entry.link.length > 0)
.map((entry) => ({
"@type": "DefinedTerm",
name: entry.name,
description: entry.description,
url: `${SITE}${withTrailingSlash(entry.link)}`,
inDefinedTermSet: TERMSET_ID,
})),
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{"@type": "ListItem", position: 1, name: "Home", item: `${SITE}/`},
{"@type": "ListItem", position: 2, name: "Docs", item: `${SITE}/docs/`},
{"@type": "ListItem", position: 3, name: "Concepts", item: `${SITE}/docs/concepts/`},
{"@type": "ListItem", position: 4, name: "Glossary", item: GLOSSARY_URL},
],
},
];

function Glossary() {
const [selectedletter, setselectedletter] = useState([]);

Expand Down Expand Up @@ -37,10 +92,17 @@ function Glossary() {

return (
<Layout
title="Glossary"
title="Software Testing Glossary — Keploy Documentation"
permalink="/reference/glossary"
description="A glossary of terms related to software testing and development."
description="Definitions for software testing, test automation, and QA terminology. Acceptance, agile unit, BDD, beta, black-box testing and more."
>
<Head>
{glossaryStructuredData.map((schema, i) => (
<script key={i} type="application/ld+json">
{JSON.stringify(schema)}
</script>
))}
</Head>
<main className="container mx-auto my-12 px-4 sm:px-6 lg:px-8">
<div className="mb-12 text-center">
<h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl">
Expand Down
20 changes: 15 additions & 5 deletions src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,21 @@ export default function Home() {
],
}
: null;
// SEO: docs landing previously rendered with title "Keploy Documentation" (20c)
// and meta description "API Test Generator Tool" (23c). Both were too short
// to capture the intent of a docs visitor (install, capture, replay, SDK).
// The Article JSON-LD below derives its `headline`/`description` from these
// same constants so the schema, the rendered <title>, the meta description
// and the sr-only H1 all agree — single source of truth.
const docsHomeTitle = "Keploy Documentation — Install, Capture & Replay API Tests";
const docsHomeDescription = "Install Keploy in 5 minutes, capture real API traffic with eBPF, and replay it as deterministic tests in CI. Quickstarts, SDK references, and integration guides.";
const articleSchema =
docsUrl && siteConfig.title
docsUrl
? {
"@context": "https://schema.org",
"@type": "Article",
headline: siteConfig.title,
description: siteConfig.tagline,
headline: docsHomeTitle,
description: docsHomeDescription,
mainEntityOfPage: {
"@type": "WebPage",
"@id": docsUrl,
Expand All @@ -62,6 +70,7 @@ export default function Home() {
},
}
: null;

return (
<div className="main">
<Head>
Expand All @@ -78,10 +87,11 @@ export default function Home() {
</Head>
<Layout
className="mx-auto my-2 w-full max-w-screen-lg px-8 shadow-none"
title={`${siteConfig.title}`}
description={`${siteConfig.tagline}`}
title={docsHomeTitle}
description={docsHomeDescription}
>
<main className="mx-auto max-w-screen-lg p-6 md:p-10">
<h1 className="sr-only">{docsHomeTitle}</h1>

<GetStartedPaths />
<TestingCapabilities />
Expand Down
2 changes: 1 addition & 1 deletion static/data/glossaryEntries.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export const glossaryEntries = {
S: [
{
name: "Stubs",
ink: "/docs/concepts/reference/glossary/stubs",
link: "/docs/concepts/reference/glossary/stubs",
description: "Simulates methods or APIs during testing.",
},
{
Expand Down
Loading
Loading