# Automate the screenshot generation process for docs - #35830

**URL:** <https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114>\
**Category:** Google Season of Code\
**Created:** [May 26, 2025, 2:58pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114 "2025-05-26T14:58:53Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![marcorichetta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/marcorichetta/32/26473_2.png) [@marcorichetta](https://forum.djangoproject.com/u/marcorichetta)\
**Post date:** [May 26, 2025, 2:58pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/1 "2025-05-26T14:58:53Z")

</div>

Continuing the discussion from this comment: [GSoC 25 Proposal Draft: Automate Processes Within Django Contribution Workflow - #11 by marcorichetta](https://forum.djangoproject.com/t/gsoc-25-proposal-draft-automate-processes-within-django-contribution-workflow/39723/11)

I’ll use this topic as a backlog of the process of contributing to ticket [#35380](https://code.djangoproject.com/ticket/35380)

#### Problem

- The Django tutorial currently has screenshots that are often outdated.
- There is no step-by-step process for generating them. It must be done manually like in @sarahboyce’s [PR #18290](https://github.com/django/django/pull/18290)

#### Proposal

- Automate this series of steps so that they can be executed with a command - `make generate-screenshots` or something similar.
- [WIP PR #19514](https://github.com/django/django/pull/19514)

#### Suggestions

- [Tim Graham](https://code.djangoproject.com/ticket/35380#comment:4) suggests that running the process only on major releases would be sufficient.

#### Questions

- Where should the `news/` folder be located?
- Can we [compress images](https://github.com/django/django/pull/18290#issuecomment-2189403966) as part of the process?
- How can I run this process on demand?

#### Todo

- Fix failing errors (`./runtests.py --selenium=firefox,chrome --headless --screenshots`)
- Fix suggestions per [PR #18211](https://github.com/django/django/pull/18211)
- Correctly generate admin docs images - [Github link](https://github.com/django/django/tree/fb6050e7845fe1a5fa131708be65ad89a31a2633/docs/ref/contrib/admin/_images)
- Correctly generate admin tutorial images - [Github link](https://github.com/django/django/tree/fb6050e7845fe1a5fa131708be65ad89a31a2633/docs/intro/_images)
- Fix misplaced `<select>` behaviour (nice to have)

---

<div class="post-metadata">

**Author:** ![marcorichetta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/marcorichetta/32/26473_2.png) [@marcorichetta](https://forum.djangoproject.com/u/marcorichetta)\
**Post date:** [May 26, 2025, 4:12pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/2 "2025-05-26T16:12:50Z")

</div>

1. Debugging selenium tests

- Pass `--pdb` and `parallel=1`
- Also `-k` for specifying a test
- `./runtests.py --selenium=chrome --screenshots --pdb --parallel=1 -k test_flatpages_fieldsets`

* * *

1. I see that tests under `news/` override [take\_screenshot from SeleniumTestCase](https://github.com/marcorichetta/django/blob/7d89b116647d5498c5d43a9aa355c7074a73d865/django/test/selenium.py#L231)  
I’m not sure what to do about that.

2. I think I should create another PR because I can’t use #18211.

3. My screenshots are generated using dark mode, inherited from my OS settings. How to avoid this?

---

<div class="post-metadata">

**Author:** ![boxed](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/boxed/32/11975_2.png) [@boxed](https://forum.djangoproject.com/u/boxed)\
**Post date:** [May 27, 2025, 5:24am UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/3 "2025-05-27T05:24:08Z")

</div>

Another approach could be to not take screenshots, but grab the generated HTML, and insert that into the docs. We do this with the iommi docs and it has some nice benefits. I have a short writeup of it here: [Don’t automate screenshots, automate iframes | En kodare](https://kodare.net/2025/01/14/iframes-not-screenshots.html)

---

<div class="post-metadata">

**Author:** ![jrief](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/jrief/32/17040_2.png) [@jrief](https://forum.djangoproject.com/u/jrief)\
**Post date:** [May 27, 2025, 8:59am UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/4 "2025-05-27T08:59:38Z")

</div>

Based on Carlton’s [django-sphinx-view](https://github.com/carltongibson/django-sphinx-view), I modified that package and added the possibility to integrate Django views directly into the documentation. This allows users to interactively test the views rather than relying on a static screenshot. It also is safer for developers, because the shown code snippets are actually executed in that environment.

This for instance is a representation of a working slug input field using those special Sphinx directives:

> **[26. Slug Input Fielddjango-formset](https://django-formset.fly.dev/slug-input/)**

And this is my fork of Carlton’s repository:

> **[GitHub - jrief/django-sphinx-view: Serve your Sphinx docs with Django.](https://github.com/jrief/django-sphinx-view)**
>
> Serve your Sphinx docs with Django.

Btw: In Playwright, creating screenshots is really easy and could be automated.

---

<div class="post-metadata">

**Author:** ![nessita](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nessita/32/12194_2.png) [@nessita](https://forum.djangoproject.com/u/nessita)\
**Post date:** [May 27, 2025, 12:07pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/5 "2025-05-27T12:07:46Z")

</div>

@boxed I read the blogpost, and the approach sounds interesting. In the rendered page, how do you handle requiring a given CSS or other static resources to properly render the embedded HTML?

---

<div class="post-metadata">

**Author:** ![nessita](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nessita/32/12194_2.png) [@nessita](https://forum.djangoproject.com/u/nessita)\
**Post date:** [May 27, 2025, 12:14pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/6 "2025-05-27T12:14:08Z")

</div>

> [@marcorichetta](#):
>
> I see that tests under `news/` override [take\_screenshot from SeleniumTestCase](https://github.com/marcorichetta/django/blob/7d89b116647d5498c5d43a9aa355c7074a73d865/django/test/selenium.py#L231)  
> I’m not sure what to do about that.

This link does not work, and I can’t find the `main` branch in your fork to further check, is there any chance that your fork needs syncing with Django’s?

Could you provide the fragment of the code you are referring to?

> [@marcorichetta](#):
>
> I think I should create another PR because I can’t use #18211.

Yes, makes sense.

> [@marcorichetta](#):
>
> My screenshots are generated using dark mode, inherited from my OS settings. How to avoid this?

That’s strange because the existing selenium tests define the mode to be used, see (for example) the decorator `@screenshot_cases(["desktop_size", "mobile_size", "dark", "high_contrast"])`.

---

<div class="post-metadata">

**Author:** ![boxed](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/boxed/32/11975_2.png) [@boxed](https://forum.djangoproject.com/u/boxed)\
**Post date:** [May 27, 2025, 12:35pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/7 "2025-05-27T12:35:26Z")

</div>

We configure the style for documentation generation to use absolute URLs for the CSS and JS. It’s quite straight forward and doesn’t affect the samples in our case, but might need a regex replace o something for Django docs, depending on the situation.

---

<div class="post-metadata">

**Author:** ![marcorichetta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/marcorichetta/32/26473_2.png) [@marcorichetta](https://forum.djangoproject.com/u/marcorichetta)\
**Post date:** [May 30, 2025, 1:59pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/8 "2025-05-30T13:59:39Z")

</div>

> [@nessita](#):
>
> This link does not work, and I can’t find the `main` branch in your fork to further check, is there any chance that your fork needs syncing with Django’s?

Thanks, now it works ✅

My first question is why `take_screenshot` is being overwritten.  
This raises a second question: what’s the purpose of the `/news` app?

> <https://github.com/marcorichetta/django/blob/7d89b116647d5498c5d43a9aa355c7074a73d865/tests/news/tests.py#L39-L42>

> <https://github.com/marcorichetta/django/blob/7d89b116647d5498c5d43a9aa355c7074a73d865/django/test/selenium.py#L231-L238>

* * *

#### 2. Dark mode

It’s weird to me because the only input to the decorator comes from `SIZES = ["docs_size"]`.

I ran `./runtests.py --selenium=chrome,firefox --screenshots -k test_user_actions` and both Chrome and Firefox are in dark mode.

I’m using [EndeavourOS](https://endeavouros.com/) (Arch based)

---

<div class="post-metadata">

**Author:** ![nessita](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nessita/32/12194_2.png) [@nessita](https://forum.djangoproject.com/u/nessita)\
**Post date:** [June 11, 2025, 2:34pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/9 "2025-06-11T14:34:23Z")

</div>

> [@marcorichetta](#):
>
> My first question is why `take_screenshot` is being overwritten.

`take_screenshot` has been overriden to ensure two things:

1. the screenshot is saved in the adequate folder (the docs images3. )
2. the file has the right name, so we can update the images in the docs via running the test (i.e., no further manual action is needed)

> [@marcorichetta](#):
>
> This raises a second question: what’s the purpose of the `/news` app?

This app is necessary to generate the same screenshots that are used in the docs. If you take a look at [Admin actions | Django documentation | Django](https://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/) you see that the screenshot use this “news” app with a specific set of values. To automate the generation of these, we depend on having the proper setup.

About the dark mode, I’m not sure what is going one, but perhaps we could progress the tasks as a whole and we debug this later on?

Hopefully this helps! Thank you for working on this!

---

<div class="post-metadata">

**Author:** ![khanxmetu](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/khanxmetu/32/28600_2.png) [@khanxmetu](https://forum.djangoproject.com/u/khanxmetu)\
**Post date:** [June 12, 2025, 3:19pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/10 "2025-06-12T15:19:11Z")

</div>

Hi @marcorichetta,

I played around with nessita’s PR some time ago. Since you’re taking their PR forward, I am sharing my thoughts if it helps.

> Where should the `news/` folder be located?

This being part of doc-related tooling but not the actual tests, I think it’d make sense moving it to somewhere in `docs/` (`docs/_ext` or a new `docs/_extras`?).

> My screenshots are generated using dark mode, inherited from my OS settings. How to avoid this?

Environment-related inconsistencies are common with test suite. See: [Update `Running the Selenium tests` documentation to link to django-docker-box · Issue #40 · django/new-features · GitHub](https://github.com/django/new-features/issues/40).  
Alternatively: inject `localStorage.setItem('theme', 'light');` to force light mode.

Two more points I want to add.

First, I think model admins should be registered on custom admin site instead of the default one, as this is also how the rest of test suite is organized. Also see [AdminSite.get\_app\_list() doesn't seem to respect TransactionTestCase.available\_apps](https://forum.djangoproject.com/t/adminsite-get-app-list-doesnt-seem-to-respect-transactiontestcase-available-apps/40707) for why this might be needed.

Second, regarding the misplaced `<select>` dropdown in screenshots: I think Selenium struggles to capture for the same reason clued here [Screenshots do not include expanded \<select\> · Issue #1306 · puppeteer/puppeteer · GitHub](https://github.com/puppeteer/puppeteer/issues/1306).  
Possible workaround is to inject some CSS for [Customizable Select Element (Explainer) | Open UI](https://open-ui.org/components/customizableselect/) which recently got shipped in [Chrome 135](https://developer.chrome.com/blog/a-customizable-select). Chrome’s implementation of customizable select puts the dropdown layer under a shadow root making it appear on DOM so it gets picked up by Selenium correctly (tested on Windows).  
Note that when testing on Firefox on Windows, the dropdown doesn’t appear at all in screenshots, with or without customizable select.

---

<div class="post-metadata">

**Author:** ![marcorichetta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/marcorichetta/32/26473_2.png) [@marcorichetta](https://forum.djangoproject.com/u/marcorichetta)\
**Post date:** [June 18, 2025, 7:08pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/11 "2025-06-18T19:08:17Z")

</div>

Hey @khanxmetu, thanks for the tips!

> [@khanxmetu](#):
>
> Alternatively: inject `localStorage.setItem('theme', 'light');` to force light mode.

The problem is when running the tests, both chrome and FF use `theme: auto` by default, which results in dark mode because of my OS.

Tried with `django-docker-box`. Got some memory issues at first, then I was able to run it but then I saw [it doesn’t support screenshot generation](https://github.com/django/django-docker-box#roadmap) (yet!).

In the end I was able to fix it by forcing light mode as you suggested.

#### Model admins:

I think I get your point but I don’t know how to make what you suggest. Should I [override the default admin](https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#overriding-the-default-admin-site)?

Anyway, I read [AdminSite.get\_app\_list() doesn't seem to respect TransactionTestCase.available\_apps](https://forum.djangoproject.com/t/adminsite-get-app-list-doesnt-seem-to-respect-transactiontestcase-available-apps/40707) and maybe it could be a PR on it’s own because of some other changes on the test suite.

#### Misplaced `<select>` dropdown

I think it can be interesting to try it, since there’s no other alternative (I believe).  
Although I wouldn’t consider it a must, but rather an improvement.  
Just in case, 2/20 images from the admin docs show this problem.

---

<div class="post-metadata">

**Author:** ![marcorichetta](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/marcorichetta/32/26473_2.png) [@marcorichetta](https://forum.djangoproject.com/u/marcorichetta)\
**Post date:** [July 24, 2025, 3:05pm UTC](https://forum.djangoproject.com/t/automate-the-screenshot-generation-process-for-docs-35830/41114/12 "2025-07-24T15:05:57Z")

</div>

## Current state

> <https://github.com/django/django/pull/19514>
>
> \#### Trac ticket number
> 
> 
> \[ticket-35380\](https://code.djangoproject.com/ticke…t/35380)
> 
> Supersedes #18211 
> 
> \#### Branch description
> DRAFT: Initial attempt to programmatically generate screenshots for docs.
> 
> \#### Checklist
> \- \[x\] This PR targets the \`main\` branch. 
> \- \[x\] The commit message is written in past tense, mentions the ticket number, and ends with a period.
> \- \[x\] I have checked the "Has patch" ticket flag in the Trac system.
> \- \[x\] I have added or updated relevant tests.
> \- \[\] I have added or updated relevant docs, including release notes if applicable.
> \- \[\] I have attached screenshots in both light and dark modes for any UI changes.

- Images generated for the admin docs - the ones under `docs/ref/contrib/admin/_images/`
- 9/14 images generated for the tutorial - the ones under `docs/intro/_images/`
- [Nice to have] Fix misplaced `<select>` behavior
- [Nice to Have] Dark mode images
  - It shouldn’t be too complex.
  - The usual approach is to save images in both modes
  - Switch which one is shown depending on the user’s toggle

## Comments

1. We have 2 apps to replicate the screenshots.
  - `news/` for admin docs
  - `polls/` for admin tutorial

2. It’s actually difficult to find a reason to standardize sizes. **Most screenshots are very scoped.** I managed to programatically crop some images. It seems to get the job done.
3. I’m not sure if test isolation is met. Not that I care much since I’m thinking of this **workflow as something that should run once from e2e**
4. It may be better to organize the tests by **use case** rather than chronologically.
  1. You enter the admin (screenshot)
  2. You register a model
  3. You go back (screenshot)
  4. You create a question
  5. You go to the Question model list view (screenshot)

5. @thibaudcolas showed me how they did it declaratively in another project (💡 idea for a future iteration?)

## Questions

I left one [here](https://github.com/django/django/pull/19514/files#r2228777668) regarding modifying the admin in runtime. I’d like feedback on that.

Thanks also to @tom for his input on the Django sprints 🙌
