Skip to content

9.0.0 (2026-08-25)

Summary

The main focus of the 9.0.0 release is project rules - rules that read the whole project context instead of a single file. We added 12 project rules (argument, import, keyword and variable checks across files) and merged the separate robocop check-project command into the regular robocop check.

Beyond project rules, this is a large release with several other big changes:

  • 13 new non-project rules, including a dedicated GROUP syntax category, continue-on-failure tag rules and new spacing/documentation/variable checks.
  • Fixes for more than 35 rules, letting Robocop auto-correct many issues and replacing several redundant formatters (leaner formatters focused on alignment and whitespace).
  • Plugins - custom rules, formatters, reports and configuration can now be packaged and distributed as plugins.
  • Built-in rulesets - reference curated rule sets with extends = ["robocop:minimal"].
  • Custom reports - load and enable your own reports just like custom rules and formatters.
  • robocop config init - generate a fully documented configuration file.
  • Test case [Metadata] support (Robot Framework 7.5) across the linter and the formatter.
  • New AlignBDDStatements formatter
  • leaner, fixed-width whitespace alignment.

Breaking changes

Deprecated formatters

Removed formatters:

  • ReplaceEmptyValues (replaced by the fix for the empty-variable rule)
  • RemoveEmptySettings (replaced by the fix for the empty-* rules - LEN11-LEN26, LEN29-LEN31, TAG08, MISC02)
  • NormalizeComments (replaced by the fix for the missing-space-after-comment rule)
  • ReplaceRunKeywordIf (replaced by the fix for the deprecated-run-keyword-if (DEPR08) rule)
  • NormalizeAssignments (replaced by the fixes for the inconsistent-assignment (MISC04) and inconsistent-assignment-in-variables (MISC05) rules.)
  • DiscardEmptySections (replaced by the fix for the empty-section (LEN09) rule. Note that, unlike the formatter's allow_only_comments=False option, the rule fix never removes sections that contain comments.)
  • ReplaceBreakContinue (replaced by the fix for the deprecated-loop-keyword (DEPR09) rule)

The functionality of the above formatters is now fully replaced by the corresponding rule fixes, and those formatters became redundant. It's part of an ongoing process to deliver more rule fixes while keeping formatters leaner. Ideally, a formatter should focus on alignment or whitespace, not on replacing/rewriting the code.

Deprecated if-can-be-used rule

It only worked for Robot Framework versions we don't support anymore. Its functionality is replaced by the deprecated-run-keyword-if rule.

Deprecated robocop check-project command

This release focuses on implementing project rules. It became clear that keeping project rules in a separate command does not make sense. It requires the user to run robocop twice, merge reports, etc. Now you can run project rules by simply selecting them by name/id or using the 'PROJECT' keyword (robocop check --select PROJECT).

Project checkers have a new signature

Refactored how a project checker works, which may affect custom rules. Now the scan_project method accepts a context argument which contains the accumulated project context (it's now done automatically by robocop if a project rule is enabled).

Leaner whitespace alignment – fixed width

Robocop used a special algorithm when calculating whitespace alignment in various formatters. The idea was to keep the whole width of a 'column' (setting/name + whitespace) as a multiple of 4. It made writing the code slightly easier (so you could more easily TAB to the next column) but was confusing to users and also produced wider lines than necessary.

Now the separator uses a fixed minimum of 4 spaces (the default min_separator). For example, if you had a 6-character-long name:

${VAR}  value

Previously, the column was rounded up to the next multiple of 4, so it was aligned to at least 12 characters (6 + 4 = 10, rounded up to 12):

${VAR}      value

And now it is simply 10 characters (6 + 4) at minimum (the line will still be aligned to the other lines as well):

${VAR}    value

While it is not a breaking change itself, it may result in multiple file changes after the Robocop update.

Features

New project rules

The main focus in 9.0.0 is project rules. Robocop is now capable of reading project context and uses it for a set of new rules.

To support it, we now have several new commands and options:

  • robocop list rules --filter PROJECT lists all project rules
  • --project/--no-project to enable/disable running project rules. By default, robocop runs project rules if they are selected, but it may be disabled with --no-project
  • --analyze-libraries/--no-analyze-libraries - robocop imports libraries to check the keywords they provide. Use this option to disable it if you're using project rules but do not want to import libraries (in exchange, robocop will not find related issues)
  • --load-library-timeout <SECONDS> - maximum time for importing a single library (default 10). Only used together with --library-workers
  • --library-workers/--no-library-workers - use it to import libraries in parallel, in a separate process with a timeout. It is useful when you have multiple libraries that take a long time to import
  • --ignored-library <NAME> - library that should not be imported. Supports glob patterns
  • --pythonpath/-P <PATH> - additional locations to search for resources, variable files and libraries. Equivalent of the Robot Framework option.
  • --variable/-v <NAME:VALUE> - set a variable used to resolve dynamic import paths. Equivalent of the Robot Framework option.
  • --variablefile/-V <NAME:VALUE> - Python or YAML file with variables used to resolve dynamic import paths. Equivalent of the Robot Framework option.

All project rules are optional – not everything may fit the code guidelines in your project.

The following rules were added:

  • ARG08 invalid-argument-count: Keyword is called with a wrong number of arguments

Example of rule violation:

*** Test Cases ***
Scenario 1
    Keyword Call    1

*** Keywords ***
Keyword Call
    [Arguments]    ${arg1}    ${arg2}
  • ARG09 missing-argument-name: Keyword is called with a positional argument instead of a named one

Example of rule violation:

*** Test Cases ***
Scenario 1
    Keyword Call    arg1=value    value2

*** Keywords ***
Keyword Call
    [Arguments]    ${arg1}    ${arg2}
  • DUP11 duplicated-variable-in-project: Variable with the same name defined in multiple files visible together

Robot Framework does not report an error when the same variable is defined in a suite and in a resource file imported by it, or in two resource files imported by the same suite. The value used at runtime depends on the import order, which makes such duplications a common source of hard to debug problems.

Example of rule violation:

*** Settings ***
Resource    variables.resource

*** Variables ***
${BROWSER}    firefox  # variables.resource also defines ${BROWSER}
  • IMP05 unused-resource-import: Imported resource file is not used.

Reports resource imports whose keywords and variables are never used in the importing file.

Example of rule violation:

*** Settings ***
Resource    unused.resource  # nothing from this file is used

*** Test Cases ***
Test
    Keyword From Other Resource
  • IMP06 unused-library-import: Imported library is not used

Reports library imports whose keywords are never used in the importing file.

Example of rule violation:

*** Settings ***
Library    Collections  # no keyword from this library is used

*** Test Cases ***
Test
    Log    message
  • IMP07 unresolved-resource-import: Imported resource file does not exist

Reports resource imports that point to a file that cannot be found in the project. Such import makes the whole suite fail during the execution.

Example of rule violation:

*** Settings ***
Resource    does_not_exist.resource  # file is not found next to the importing file
  • IMP08 circular-import: Resource file is a part of a circular import

Reports resource imports that import, directly or indirectly, the file they are used in.

Example of rule violation:

# keywords.resource
*** Settings ***
Resource    helpers.resource

# helpers.resource
*** Settings ***
Resource    keywords.resource  # keywords.resource imports this file already
  • IMP09 unresolved-library-import: Imported library could not be imported

Reports library imports that Robot Framework would not be able to import during the execution.

Example of rule violation:

*** Settings ***
Library    libs/does_not_exist.py  # file is not found next to the importing file
Library    NotInstalledLibrary  # module is not installed and is not found in the search paths
  • KW04 unused-keyword: Keyword is not used

Reports keywords that are defined in the project but never called.

Example:

*** Test Cases ***
Test that only non used keywords are reported
    Used Keyword

*** Keywords ***
Used Keyword
    Log    used

Not Used Keyword  # this keyword will be reported as not used
    [Arguments]    ${arg}
    Should Be True    ${arg}>50
  • KW05 keyword-not-found: Keyword is not defined anywhere

Reports keyword calls that do not match any keyword defined in the file, in the imported resource files or in the imported libraries. Robot Framework fails such call with the No keyword with name 'X' found error.

Example of rule violation:

*** Settings ***
Resource    login.resource   # defines Login

*** Test Cases ***
Test
    Login    user    password
    Logout                    # Logout is not defined anywhere
  • KW06 ambiguous-keyword-name: Keyword name matches more than one keyword

Reports keyword calls that match keywords defined in more than one place. Robot Framework fails such call with the Multiple keywords with name 'X' found error, unless the call uses the full name of the keyword.

Example of rule violation:

*** Settings ***
Resource    login.resource      # defines Login
Resource    admin.resource      # defines Login as well

*** Test Cases ***
Test
    Login    user    password   # it is not known which keyword should be used
  • KW07 missing-keyword-prefix: Keyword is called without the name of the resource file or library it comes from

Optional rule for projects that require every keyword call to be prefixed with the source of the keyword. Such calls are unambiguous and it is immediately clear where the keyword comes from:

*** Settings ***
Resource       login.resource
Library        SeleniumLibrary

*** Test Cases ***
Test
    Login    user    password        # will be reported
    Click Element    id:submit       # will be reported

    login.Login    user    password  # explicit, not reported
    SeleniumLibrary.Click Element    id:submit

New AlignBDDStatements formatter (#1451)

New, non-default formatter that aligns BDD statements in the test case body. Keyword calls prefixed with the BDD reserved keywords (Given, When, And, But and Then) are indented so the keyword names following the prefixes are aligned in a single column:

*** Test Cases ***
There can be only one
    Given there are 3 ninjas
      And there are more than one ninja alive
     When 2 ninjas meet, they will fight
     Then one ninja dies (but not me)
      And there is one ninja less alive

Enable it with robocop format --select AlignBDDStatements. See AlignBDDStatements documentation for more details.

AlignTemplatedTestCases arguments placement (args_with_test)

AlignTemplatedTestCases now exposes an args_with_test parameter that controls whether template arguments and settings stay in the same line as the test case name. Possible values are split (new default), split_on_settings and keep:

  • split always moves template arguments and settings to their own line, keeping only the test case name in the first line. Test case names are ignored when calculating the column widths (header names are still respected) and settings are no longer counted towards the column widths.
  • split_on_settings moves template arguments and settings to their own line only if the test case contains any setting (such as [Tags] or [Documentation]).
  • keep keeps template arguments and settings in the same line as the test case name (the previous behaviour). When header names are present, the first body row is pulled up to the test case name line.

Test cases that contain block structures (FOR, IF, TRY, WHILE) are always split, regardless of the selected mode. Additionally, [Documentation] no longer affects the calculated column widths.

Since the default changed to split, updating Robocop may reformat existing templated test cases. Configure AlignTemplatedTestCases.args_with_test=keep to preserve the previous layout. See AlignTemplatedTestCases documentation for more details.

Support for test case metadata (Robot Framework 7.5)

Upcoming Robot Framework 7.5 allows using the [Metadata] setting inside test cases:

*** Test Cases ***
Test
    [Documentation]    Doc.
    [Metadata]    Requirement    REQ-42
    [Metadata]    Component      Login
    Keyword

Robocop now recognizes it in both the linter and the formatter:

  • [Metadata] is accepted by ERR09 setting-not-supported when running with Robot Framework 7.5 or newer
  • DUP06 duplicated-metadata now reports duplicated metadata per test case, instead of mixing test case metadata with the suite metadata. Previously, the same metadata name used in two different test cases was incorrectly reported as a duplicate
  • LEN11 empty-metadata and the new LEN33 metadata-without-value rule work with test case metadata
  • NAME05 setting-name-not-in-title-case now also validates the Metadata/[Metadata] setting name
  • SPC19 not-enough-whitespace-after-setting now also reports the [Metadata] setting
  • ORD01 test-case-section-out-of-order supports the new metadata value. The default order is now documentation,metadata,tags,timeout,setup,template,keyword,teardown
  • OrderSettings formatter orders [Metadata] right after [Documentation]. Its test_before default is now documentation,metadata,tags,timeout,setup,template. The formatter also no longer removes repeated settings, which previously would have dropped all but the last [Metadata]
  • AlignTestCasesSection formatter aligns [Metadata] together with the other test case settings. It can be disabled with --skip metadata or AlignTestCasesSection.skip_metadata=True

New rules

GROUP syntax rules (#1159)

Robot Framework 7.2 added the GROUP syntax. Robocop now ships a dedicated GRP rule category for it:

  • GRP01 too-few-calls-in-group: GROUP with too few keyword calls inside (configurable with min_calls, defaults to 2 so groups wrapping a single keyword are reported)
  • GRP02 too-many-calls-in-group: GROUP with too many keyword calls inside (configurable with max_calls, defaults to 10)
  • GRP03 group-without-name: GROUP used without a name
  • GRP04 nested-group: GROUP nested inside another GROUP (disabled by default)

Empty and unterminated GROUP blocks are now reported by the parsing-error rule.

Disallow the GROUP syntax (#1158)

  • GRP05 group-not-allowed: reports every use of the GROUP syntax (disabled by default). Enable it to keep your project compatible with Robot Framework versions older than 7.2 or when your team decided not to use groups.

continue-on-failure tag rules (#1882)

Rules related to robot:continue-on-failure tag:

  • TAG12 unnecessary-continue-on-failure reports Run Keyword And Continue On Failure calls in tests and keywords that already enable the continue on failure mode with a tag.
  • TAG13 could-be-continue-on-failure-tag reports tests and keywords where every keyword call is wrapped in Run Keyword And Continue On Failure and the robot:continue-on-failure tag could be used instead.

empty template data line rule (#999)

New SPC23 empty-line-in-test-template rule (with a fix) that reports on:

*** Settings ***
Test Template    Template Keyword


*** Test Cases ***
Suite template
    first

    second

Metadata without value rule

New LEN33 metadata-without-value reports metadata with a name but without a value.

Example of rule violation:

*** Settings ***
Metadata    Version

*** Test Cases ***
Test
    [Metadata]    Requirement

Such metadata is resolved to an empty value, which is almost always a mistake. Use LEN11 empty-metadata to detect metadata without a name.

Empty lines inside block rule (#1891)

New SPC24 empty-lines-inside-block that reports on:

*** Keywords ***
Iterate
    FOR    ${var}    IN    1    2

        Keyword Call

    END

Automatic variable availability rule (#1867)

New VAR13 automatic-variable-not-available rule that reports automatic variables used in a context where they are not available (for example, using ${TEST_NAME} outside of a test case).

Variable in documentation rule (#1866)

New DOC05 variable-in-documentation rule (disabled by default) that detects unescaped variables used in documentation.

Whitespace around comparison operator rule (#1762)

New MISC16 not-enough-whitespace-around-operator rule that reports comparison operators (==, !=, >, <, >=, <=) used in conditions without surrounding whitespace. It inspects the conditions of IF and WHILE blocks as well as the conditions passed to BuiltIn keywords such as Should Be True or Skip If. The missing whitespace can be added automatically with the --fix option:

*** Test Cases ***
Test
    IF    ${variable}==5    # reported: should be '${variable} == 5'
        Log    Robocop
    END

New fixes for rules

Several rules now have fixes:

  • deprecated-run-keyword-if (#1898)
  • inconsistent-assignment
  • inline-if-can-be-used
  • duplicated-variable (#1857)
  • else-not-upper-case (#1854)
  • empty-return (#1846)
  • empty-section (#1841)
  • empty-tags (#1842)
  • unnecessary-default-tags (#1850)
  • unused-disabler (#1855)
  • missing-space-after-comment (#1844)
  • ignored-data (#1844)
  • inconsistent-assignment-in-variables (#1859)
  • misplaced-negative-condition (#1859)
  • deprecated-with-name (#1843)
  • deprecated-singular-header (#1843)
  • duplicated-resource (#1848)
  • duplicated-library (#1848)
  • duplicated-variables-import (#1848)
  • empty-* 18 rules (#1840)
  • empty-library-alias (#1847)
  • duplicated-library-alias (#1847)
  • empty-variable (#1852)
  • undefined-argument-default (#1852)
  • wrong-import-order (#1856)
  • builtin-imports-not-sorted (#1856)
  • tag-already-set-in-test-tags (#1851)
  • duplicated-tags (#1851)
  • tag-already-set-in-keyword-tags (#1851)
  • setting-name-not-in-title-case (#1858)
  • section-name-invalid (#1858)
  • wrong-case-in-keyword-name (#1860)
  • wrong-case-in-keyword-call (#1860)
  • not-enough-whitespace-after-setting (#1862)
  • not-enough-whitespace-after-newline-marker (#1862)
  • not-enough-whitespace-after-variable (#1862)

Plugins (#1538)

Custom rules, formatters and configuration files can now be packaged and distributed as a Robocop plugin. A plugin is a regular Python package that registers itself using the robocop.plugins entry point group:

pyproject.toml
[project]
name = "example-plugin"

[project.entry-points."robocop.plugins"]
example = "example_plugin.path.to.dir"

Robocop discovers installed plugins automatically. Use robocop list plugins to see them. The entry point name becomes the plugin namespace, and its resources are referenced with the <plugin_name>.<path.inside.the.plugin> syntax:

[tool.robocop]
extends = ["example.config.strict"]

[tool.robocop.lint]
custom_rules = ["example.rules"]
reports = ["example.reports"]

[tool.robocop.format]
select = ["example.formatters.FormatterA"]

Installing a plugin does not enable anything on its own - it only registers the namespace, so plugin resources can be referenced in the configuration.

Note that extends values that do not end with .toml were previously silently ignored. They are now resolved as plugin references, and Robocop reports an error if the plugin or its configuration file is missing.

See the plugins documentation for more details.

Built-in rulesets (#1523)

Robocop ships with built-in rulesets that can be referenced using the robocop:<name> syntax in extends:

[tool.robocop]
extends = ["robocop:minimal"]

Currently, there is only one built-in ruleset available - robocop:minimal, a curated, minimal set of rules focused on correctness and runtime issues. It selects only the rules from this set and overrides their default severity with the one defined by the ruleset.

Just like any other configuration, a built-in ruleset can be extended and overridden. For example, to use the minimal ruleset but add an extra rule:

[tool.robocop]
extends = ["robocop:minimal"]

[tool.robocop.lint]
select = ["line-too-long"]

As part of this change, several rules have updated severity values (in default configuration). We will monitor and analyze the best mappings and add next rulesets in upcoming releases.

Custom reports (#1115)

Reports can now be defined outside Robocop, in the same way as the custom rules and formatters. Custom reports are enabled with the existing --reports option by pointing it to the source of the reports: a path to the Python file or a directory, an importable module or a plugin reference:

robocop check --reports path/to/custom_report.py
robocop check --reports example.reports
[tool.robocop.lint]
reports = [
    "all",
    "path/to/custom_report.py"
]

All reports found in the source are loaded and enabled. Custom report is a class that inherits from the robocop.linter.reports.Report class and defines the name and the description attributes. Such report can be configured (--configure custom_report.param=value), listed (robocop list reports) and documented (robocop docs custom_report) exactly like the built-in reports.

See the reports documentation for more details.

Generate a configuration file (#897)

New robocop config init command generates a documented configuration file with all the available options. The generated robocop.toml lists every global option, linter rule and formatter together with their default values and a short description. All options are written as comments, so the generated file reproduces Robocop's default behaviour until you uncomment and edit the options you want to change:

robocop config init                          # write robocop.toml in the current directory
robocop config init --force                  # overwrite an existing file
robocop config init --output config.toml     # write to a custom location
robocop config init --output -               # print to the standard output

See the configuration documentation for more details.

Fixes

  • Run Keywords variants are now properly resolved when used with a BDD prefix (#1877)
  • empty and unterminated GROUP parse errors are now reported (#1895)
  • replaced the deprecated GitWildMatchPattern with GitIgnoreSpec (#1887)
  • not-allowed-char-in-filename (file-wide rule) prints source code on report (#1871)
  • check the VAR assignment sign in the inconsistent-assignment rule (#1902)
  • strip whitespace in comma-separated formatter parameters so values like imports_order=library, resource, variables are accepted (#1922)

Other

  • allow to skip report file generation on empty results (#1835)
  • inconsistent-assignment (MISC04) rule now also checks the assignment sign in the VAR syntax
  • add ignored_docs parameter to line-too-long (#1892)
  • align only first column of documentation in AlignSettingsSection (#1890)
  • keep comments together with the closest keyword/argument in IndentNestedKeywords (#1919)

Refactors

Combined several checkers (for better control and performance), split rules from checkers with dedicated responsibility (visitors separately from rule checks) in the following PRs:

  • merge control flow checkers into single ControlFlowChecker (#1818)
  • merge deprecated statement checker into settings checker (#1820)
  • merge keyword and variable naming checkers (#1819)
  • merge keyword argument checkers into a single checker (#1808)
  • merge keyword body checkers into a single checker (#1812)
  • merge keyword call checkers into a single checker (#1807)
  • merge raw file checkers into a single checker (#1806)
  • merge section-level checkers into single SectionsChecker (#1813)
  • merge settings checkers into single SettingsChecker (#1817)
  • merge tag checkers into single TagsChecker (#1815)
  • merge test case and keyword checkers into TestCaseKeywordChecker (#1814)
  • merge variable statement checkers into single VariablesChecker (#1816)
  • move linter checkers to a dedicated package (#1821)